Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

1
vendor/jobserver/.cargo-checksum.json vendored Normal file
View File

@@ -0,0 +1 @@
{"files":{"Cargo.lock":"9bd696e6220179a66678d59d6551022baf3c68275eebcfb5c3ef515669e77592","Cargo.toml":"4bcf99a15aa067eea875183e47b015b943599b4102d95a07ab4bdce255548ee4","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","README.md":"de412b60535de63bd312ab0df197f72b89d6aaeab166ed090f58a26dcda72bcf","src/error.rs":"f947d858ba95a9a08d16325d163b61fdf0ce40ec95b7fb08cc47c9876de9c8dc","src/lib.rs":"9b82f32a87deae5bd5a0ed36cdab3a195f9419c6befa9f77a682f262b9158722","src/unix.rs":"353cc1b9653bc30e889522dcf31c9f0d1ae120ce4c1248dfc647ea8435171cad","src/wasm.rs":"74db7bd1441b7b4d1b53cd83e23097a2b976faf4d18d3f42f583048d27611d4d","src/windows.rs":"d939574ff187be4fa6bfba4cb9f2cb95541dc8752d0e2915c8b9de90bd833755","tests/client-of-myself.rs":"ca09bf398f69df4bac1730999e954dbbc3faf3c6512678c136e0938e7e9cd0ab","tests/client.rs":"5d9b3606356c0fed0c9f5bf372f0fa09f922f93ffe910a1a9a474898d5ec9fa2","tests/helper.rs":"7b8d9eb8484ccf1e656ed235a23f62c2756045e3a6604121aee5493d8a5b1ede","tests/make-as-a-client.rs":"8be1f3fef1e9e65c7904dbaa04364bf0f44e9deab84a2a247a5a94b5cf0df9bc","tests/server.rs":"da15bf12e1df1883f660892b996c9e0d92485aace3f7b50ee70c4a8e6deae8da"},"package":"9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"}

198
vendor/jobserver/Cargo.lock generated vendored Normal file
View File

@@ -0,0 +1,198 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "bitflags"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "getrandom"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi",
]
[[package]]
name = "jobserver"
version = "0.1.34"
dependencies = [
"getrandom",
"libc",
"nix",
"tempfile",
]
[[package]]
name = "libc"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "linux-raw-sys"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
[[package]]
name = "nix"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "rustix"
version = "0.38.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "tempfile"
version = "3.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [
"cfg-if",
"fastrand",
"rustix",
"windows-sys",
]
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
]

72
vendor/jobserver/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,72 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.63"
name = "jobserver"
version = "0.1.34"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = """
An implementation of the GNU Make jobserver for Rust.
"""
homepage = "https://github.com/rust-lang/jobserver-rs"
documentation = "https://docs.rs/jobserver"
readme = "README.md"
license = "MIT OR Apache-2.0"
repository = "https://github.com/rust-lang/jobserver-rs"
[lib]
name = "jobserver"
path = "src/lib.rs"
[[test]]
name = "client"
path = "tests/client.rs"
harness = false
[[test]]
name = "client-of-myself"
path = "tests/client-of-myself.rs"
harness = false
[[test]]
name = "helper"
path = "tests/helper.rs"
[[test]]
name = "make-as-a-client"
path = "tests/make-as-a-client.rs"
harness = false
[[test]]
name = "server"
path = "tests/server.rs"
[dev-dependencies.tempfile]
version = "3.10.1"
[target."cfg(unix)".dependencies.libc]
version = "0.2.171"
[target."cfg(unix)".dev-dependencies.nix]
version = "0.28.0"
features = ["fs"]
[target."cfg(windows)".dependencies.getrandom]
version = "0.3.2"
features = ["std"]

201
vendor/jobserver/LICENSE-APACHE vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

25
vendor/jobserver/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,25 @@
Copyright (c) 2014 Alex Crichton
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.

33
vendor/jobserver/README.md vendored Normal file
View File

@@ -0,0 +1,33 @@
# jobserver-rs
An implementation of the GNU Make jobserver for Rust.
[![crates.io](https://img.shields.io/crates/v/jobserver.svg?maxAge=2592000)](https://crates.io/crates/jobserver)
[Documentation](https://docs.rs/jobserver)
## Usage
Add this to your `Cargo.toml`:
```toml
[dependencies]
jobserver = "0.1"
```
# License
This project is licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
https://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
https://opensource.org/license/mit)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in jobserver-rs by you, as defined in the Apache-2.0 license,
shall be dual licensed as above, without any additional terms or conditions.

95
vendor/jobserver/src/error.rs vendored Normal file
View File

@@ -0,0 +1,95 @@
#[cfg(unix)]
type RawFd = std::os::unix::io::RawFd;
#[cfg(not(unix))]
type RawFd = std::convert::Infallible;
/// Error type for [`Client::from_env_ext`] function.
///
/// [`Client::from_env_ext`]: crate::Client::from_env_ext
#[derive(Debug)]
pub struct FromEnvError {
pub(crate) inner: FromEnvErrorInner,
}
/// Kind of an error returned from [`Client::from_env_ext`] function.
///
/// [`Client::from_env_ext`]: crate::Client::from_env_ext
#[derive(Debug)]
#[non_exhaustive]
pub enum FromEnvErrorKind {
/// There is no environment variable that describes jobserver to inherit.
NoEnvVar,
/// There is no jobserver in the environment variable.
/// Variables associated with Make can be used for passing data other than jobserver info.
NoJobserver,
/// Cannot parse jobserver environment variable value, incorrect format.
CannotParse,
/// Cannot open path or name from the jobserver environment variable value.
CannotOpenPath,
/// Cannot open file descriptor from the jobserver environment variable value.
CannotOpenFd,
/// The jobserver style is a simple pipe, but at least one of the file descriptors
/// is negative, which means it is disabled for this process
/// ([GNU `make` manual: POSIX Jobserver Interaction](https://www.gnu.org/software/make/manual/make.html#POSIX-Jobserver)).
NegativeFd,
/// File descriptor from the jobserver environment variable value is not a pipe.
NotAPipe,
/// Jobserver inheritance is not supported on this platform.
Unsupported,
}
impl FromEnvError {
/// Get the error kind.
pub fn kind(&self) -> FromEnvErrorKind {
match self.inner {
FromEnvErrorInner::NoEnvVar => FromEnvErrorKind::NoEnvVar,
FromEnvErrorInner::NoJobserver => FromEnvErrorKind::NoJobserver,
FromEnvErrorInner::CannotParse(_) => FromEnvErrorKind::CannotParse,
FromEnvErrorInner::CannotOpenPath(..) => FromEnvErrorKind::CannotOpenPath,
FromEnvErrorInner::CannotOpenFd(..) => FromEnvErrorKind::CannotOpenFd,
FromEnvErrorInner::NegativeFd(..) => FromEnvErrorKind::NegativeFd,
FromEnvErrorInner::NotAPipe(..) => FromEnvErrorKind::NotAPipe,
FromEnvErrorInner::Unsupported => FromEnvErrorKind::Unsupported,
}
}
}
impl std::fmt::Display for FromEnvError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.inner {
FromEnvErrorInner::NoEnvVar => write!(f, "there is no environment variable that describes jobserver to inherit"),
FromEnvErrorInner::NoJobserver => write!(f, "there is no `--jobserver-fds=` or `--jobserver-auth=` in the environment variable"),
FromEnvErrorInner::CannotParse(s) => write!(f, "cannot parse jobserver environment variable value: {s}"),
FromEnvErrorInner::CannotOpenPath(s, err) => write!(f, "cannot open path or name {s} from the jobserver environment variable value: {err}"),
FromEnvErrorInner::CannotOpenFd(fd, err) => write!(f, "cannot open file descriptor {fd} from the jobserver environment variable value: {err}"),
FromEnvErrorInner::NegativeFd(fd) => write!(f, "file descriptor {fd} from the jobserver environment variable value is negative"),
FromEnvErrorInner::NotAPipe(fd, None) => write!(f, "file descriptor {fd} from the jobserver environment variable value is not a pipe"),
FromEnvErrorInner::NotAPipe(fd, Some(err)) => write!(f, "file descriptor {fd} from the jobserver environment variable value is not a pipe: {err}"),
FromEnvErrorInner::Unsupported => write!(f, "jobserver inheritance is not supported on this platform"),
}
}
}
impl std::error::Error for FromEnvError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.inner {
FromEnvErrorInner::CannotOpenPath(_, err) => Some(err),
FromEnvErrorInner::NotAPipe(_, Some(err)) | FromEnvErrorInner::CannotOpenFd(_, err) => {
Some(err)
}
_ => None,
}
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub(crate) enum FromEnvErrorInner {
NoEnvVar,
NoJobserver,
CannotParse(String),
CannotOpenPath(String, std::io::Error),
CannotOpenFd(RawFd, std::io::Error),
NegativeFd(RawFd),
NotAPipe(RawFd, Option<std::io::Error>),
Unsupported,
}

704
vendor/jobserver/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,704 @@
//! An implementation of the GNU make jobserver.
//!
//! This crate is an implementation, in Rust, of the GNU `make` jobserver for
//! CLI tools that are interoperating with make or otherwise require some form
//! of parallelism limiting across process boundaries. This was originally
//! written for usage in Cargo to both (a) work when `cargo` is invoked from
//! `make` (using `make`'s jobserver) and (b) work when `cargo` invokes build
//! scripts, exporting a jobserver implementation for `make` processes to
//! transitively use.
//!
//! The jobserver implementation can be found in [detail online][docs] but
//! basically boils down to a cross-process semaphore. On Unix this is
//! implemented with the `pipe` syscall and read/write ends of a pipe and on
//! Windows this is implemented literally with IPC semaphores. Starting from
//! GNU `make` version 4.4, named pipe becomes the default way in communication
//! on Unix. This crate also supports that feature in the sense of inheriting
//! and forwarding the correct environment.
//!
//! The jobserver protocol in `make` also dictates when tokens are acquired to
//! run child work, and clients using this crate should take care to implement
//! such details to ensure correct interoperation with `make` itself.
//!
//! ## Examples
//!
//! Connect to a jobserver that was set up by `make` or a different process:
//!
//! ```no_run
//! use jobserver::Client;
//!
//! // See API documentation for why this is `unsafe`
//! let client = match unsafe { Client::from_env() } {
//! Some(client) => client,
//! None => panic!("client not configured"),
//! };
//! ```
//!
//! Acquire and release token from a jobserver:
//!
//! ```no_run
//! use jobserver::Client;
//!
//! let client = unsafe { Client::from_env().unwrap() };
//! let token = client.acquire().unwrap(); // blocks until it is available
//! drop(token); // releases the token when the work is done
//! ```
//!
//! Create a new jobserver and configure a child process to have access:
//!
//! ```
//! use std::process::Command;
//! use jobserver::Client;
//!
//! let client = Client::new(4).expect("failed to create jobserver");
//! let mut cmd = Command::new("make");
//! client.configure(&mut cmd);
//! ```
//!
//! ## Caveats
//!
//! This crate makes no attempt to release tokens back to a jobserver on
//! abnormal exit of a process. If a process which acquires a token is killed
//! with ctrl-c or some similar signal then tokens will not be released and the
//! jobserver may be in a corrupt state.
//!
//! Note that this is typically ok as ctrl-c means that an entire build process
//! is being torn down, but it's worth being aware of at least!
//!
//! ## Windows caveats
//!
//! There appear to be two implementations of `make` on Windows. On MSYS2 one
//! typically comes as `mingw32-make` and the other as `make` itself. I'm not
//! personally too familiar with what's going on here, but for jobserver-related
//! information the `mingw32-make` implementation uses Windows semaphores
//! whereas the `make` program does not. The `make` program appears to use file
//! descriptors and I'm not really sure how it works, so this crate is not
//! compatible with `make` on Windows. It is, however, compatible with
//! `mingw32-make`.
//!
//! [docs]: https://make.mad-scientist.net/papers/jobserver-implementation/
#![deny(missing_docs, missing_debug_implementations)]
#![doc(html_root_url = "https://docs.rs/jobserver/0.1")]
use std::env;
use std::ffi::OsString;
use std::io;
use std::process::Command;
use std::sync::{Arc, Condvar, Mutex, MutexGuard};
mod error;
#[cfg(unix)]
#[path = "unix.rs"]
mod imp;
#[cfg(windows)]
#[path = "windows.rs"]
mod imp;
#[cfg(not(any(unix, windows)))]
#[path = "wasm.rs"]
mod imp;
/// A client of a jobserver
///
/// This structure is the main type exposed by this library, and is where
/// interaction to a jobserver is configured through. Clients are either created
/// from scratch in which case the internal semphore is initialied on the spot,
/// or a client is created from the environment to connect to a jobserver
/// already created.
///
/// Some usage examples can be found in the crate documentation for using a
/// client.
///
/// Note that a [`Client`] implements the [`Clone`] trait, and all instances of
/// a [`Client`] refer to the same jobserver instance.
#[derive(Clone, Debug)]
pub struct Client {
inner: Arc<imp::Client>,
}
/// An acquired token from a jobserver.
///
/// This token will be released back to the jobserver when it is dropped and
/// otherwise represents the ability to spawn off another thread of work.
#[derive(Debug)]
pub struct Acquired {
client: Arc<imp::Client>,
data: imp::Acquired,
disabled: bool,
}
impl Acquired {
/// This drops the [`Acquired`] token without releasing the associated token.
///
/// This is not generally useful, but can be helpful if you do not have the
/// ability to store an Acquired token but need to not yet release it.
///
/// You'll typically want to follow this up with a call to
/// [`Client::release_raw`] or similar to actually release the token later on.
pub fn drop_without_releasing(mut self) {
self.disabled = true;
}
}
#[derive(Default, Debug)]
struct HelperState {
lock: Mutex<HelperInner>,
cvar: Condvar,
}
#[derive(Default, Debug)]
struct HelperInner {
requests: usize,
producer_done: bool,
consumer_done: bool,
}
use error::FromEnvErrorInner;
pub use error::{FromEnvError, FromEnvErrorKind};
/// Return type for [`Client::from_env_ext`] function.
#[derive(Debug)]
pub struct FromEnv {
/// Result of trying to get jobserver client from env.
pub client: Result<Client, FromEnvError>,
/// Name and value of the environment variable.
/// `None` if no relevant environment variable is found.
pub var: Option<(&'static str, OsString)>,
}
impl FromEnv {
fn new_ok(client: Client, var_name: &'static str, var_value: OsString) -> FromEnv {
FromEnv {
client: Ok(client),
var: Some((var_name, var_value)),
}
}
fn new_err(kind: FromEnvErrorInner, var_name: &'static str, var_value: OsString) -> FromEnv {
FromEnv {
client: Err(FromEnvError { inner: kind }),
var: Some((var_name, var_value)),
}
}
}
impl Client {
/// Creates a new jobserver initialized with the given parallelism limit.
///
/// A client to the jobserver created will be returned. This client will
/// allow at most `limit` tokens to be acquired from it in parallel. More
/// calls to [`Client::acquire`] will cause the calling thread to block.
///
/// Note that the created [`Client`] is not automatically inherited into
/// spawned child processes from this program. Manual usage of the
/// [`Client::configure`] function is required for a child process to have
/// access to a job server.
///
/// # Examples
///
/// ```
/// use jobserver::Client;
///
/// let client = Client::new(4).expect("failed to create jobserver");
/// ```
///
/// # Errors
///
/// Returns an error if any I/O error happens when attempting to create the
/// jobserver client.
pub fn new(limit: usize) -> io::Result<Client> {
Ok(Client {
inner: Arc::new(imp::Client::new(limit)?),
})
}
/// Attempts to connect to the jobserver specified in this process's
/// environment.
///
/// When the a `make` executable calls a child process it will configure the
/// environment of the child to ensure that it has handles to the jobserver
/// it's passing down. This function will attempt to look for these details
/// and connect to the jobserver.
///
/// Note that the created [`Client`] is not automatically inherited into
/// spawned child processes from this program. Manual usage of the
/// [`Client::configure`] function is required for a child process to have
/// access to a job server.
///
/// # Return value
///
/// [`FromEnv`] contains result and relevant environment variable.
/// If a jobserver was found in the environment and it looks correct then
/// result with the connected client will be returned. In other cases
/// result will contain `Err(FromEnvErr)`.
///
/// Additionally on Unix this function will configure the file descriptors
/// with `CLOEXEC` so they're not automatically inherited by spawned
/// children.
///
/// On unix if `check_pipe` enabled this function will check if provided
/// files are actually pipes.
///
/// # Safety
///
/// This function is `unsafe` to call on Unix specifically as it
/// transitively requires usage of the `from_raw_fd` function, which is
/// itself unsafe in some circumstances.
///
/// It's recommended to call this function very early in the lifetime of a
/// program before any other file descriptors are opened. That way you can
/// make sure to take ownership properly of the file descriptors passed
/// down, if any.
///
/// It is ok to call this function any number of times.
pub unsafe fn from_env_ext(check_pipe: bool) -> FromEnv {
let (env, var_os) = match ["CARGO_MAKEFLAGS", "MAKEFLAGS", "MFLAGS"]
.iter()
.map(|&env| env::var_os(env).map(|var| (env, var)))
.find_map(|p| p)
{
Some((env, var_os)) => (env, var_os),
None => return FromEnv::new_err(FromEnvErrorInner::NoEnvVar, "", Default::default()),
};
let var = match var_os.to_str() {
Some(var) => var,
None => {
let err = FromEnvErrorInner::CannotParse("not valid UTF-8".to_string());
return FromEnv::new_err(err, env, var_os);
}
};
let s = match find_jobserver_auth(var) {
Some(s) => s,
None => return FromEnv::new_err(FromEnvErrorInner::NoJobserver, env, var_os),
};
match imp::Client::open(s, check_pipe) {
Ok(c) => FromEnv::new_ok(Client { inner: Arc::new(c) }, env, var_os),
Err(err) => FromEnv::new_err(err, env, var_os),
}
}
/// Attempts to connect to the jobserver specified in this process's
/// environment.
///
/// Wraps [`Client::from_env_ext`] and discards error details.
///
/// # Safety
///
/// This function is `unsafe` to call on Unix specifically as it
/// transitively requires usage of the `from_raw_fd` function, which is
/// itself unsafe in some circumstances.
///
/// It's recommended to call this function very early in the lifetime of a
/// program before any other file descriptors are opened. That way you can
/// make sure to take ownership properly of the file descriptors passed
/// down, if any.
///
/// It is ok to call this function any number of times.
pub unsafe fn from_env() -> Option<Client> {
Self::from_env_ext(false).client.ok()
}
/// Acquires a token from this jobserver client.
///
/// This function will block the calling thread until a new token can be
/// acquired from the jobserver.
///
/// # Return value
///
/// On successful acquisition of a token an instance of [`Acquired`] is
/// returned. This structure, when dropped, will release the token back to
/// the jobserver. It's recommended to avoid leaking this value.
///
/// # Errors
///
/// If an I/O error happens while acquiring a token then this function will
/// return immediately with the error. If an error is returned then a token
/// was not acquired.
pub fn acquire(&self) -> io::Result<Acquired> {
let data = self.inner.acquire()?;
Ok(Acquired {
client: self.inner.clone(),
data,
disabled: false,
})
}
/// Acquires a token from this jobserver client in a non-blocking way.
///
/// # Return value
///
/// On successful acquisition of a token an instance of [`Acquired`] is
/// returned. This structure, when dropped, will release the token back to
/// the jobserver. It's recommended to avoid leaking this value.
///
/// # Errors
///
/// If an I/O error happens while acquiring a token then this function will
/// return immediately with the error. If an error is returned then a token
/// was not acquired.
///
/// If non-blocking acquire is not supported, the return error will have its `kind()`
/// set to [`io::ErrorKind::Unsupported`].
pub fn try_acquire(&self) -> io::Result<Option<Acquired>> {
let ret = self.inner.try_acquire()?;
Ok(ret.map(|data| Acquired {
client: self.inner.clone(),
data,
disabled: false,
}))
}
/// Returns amount of tokens in the read-side pipe.
///
/// # Return value
///
/// Number of bytes available to be read from the jobserver pipe
///
/// # Errors
///
/// Underlying errors from the ioctl will be passed up.
pub fn available(&self) -> io::Result<usize> {
self.inner.available()
}
/// Configures a child process to have access to this client's jobserver as
/// well.
///
/// This function is required to be called to ensure that a jobserver is
/// properly inherited to a child process. If this function is *not* called
/// then this [`Client`] will not be accessible in the child process. In
/// other words, if not called, then [`Client::from_env`] will return `None`
/// in the child process (or the equivalent of [`Client::from_env`] that
/// `make` uses).
///
/// ## Platform-specific behavior
///
/// On Unix and Windows this will clobber the `CARGO_MAKEFLAGS` environment
/// variables for the child process, and on Unix this will also allow the
/// two file descriptors for this client to be inherited to the child.
///
/// On platforms other than Unix and Windows this panics.
pub fn configure(&self, cmd: &mut Command) {
cmd.env("CARGO_MAKEFLAGS", &self.mflags_env());
self.inner.configure(cmd);
}
/// Configures a child process to have access to this client's jobserver as
/// well.
///
/// This function is required to be called to ensure that a jobserver is
/// properly inherited to a child process. If this function is *not* called
/// then this [`Client`] will not be accessible in the child process. In
/// other words, if not called, then [`Client::from_env`] will return `None`
/// in the child process (or the equivalent of [`Client::from_env`] that
/// `make` uses).
///
/// ## Platform-specific behavior
///
/// On Unix and Windows this will clobber the `CARGO_MAKEFLAGS`,
/// `MAKEFLAGS` and `MFLAGS` environment variables for the child process,
/// and on Unix this will also allow the two file descriptors for
/// this client to be inherited to the child.
///
/// On platforms other than Unix and Windows this panics.
pub fn configure_make(&self, cmd: &mut Command) {
let value = self.mflags_env();
cmd.env("CARGO_MAKEFLAGS", &value);
cmd.env("MAKEFLAGS", &value);
cmd.env("MFLAGS", &value);
self.inner.configure(cmd);
}
fn mflags_env(&self) -> String {
let arg = self.inner.string_arg();
// Older implementations of make use `--jobserver-fds` and newer
// implementations use `--jobserver-auth`, pass both to try to catch
// both implementations.
format!("-j --jobserver-fds={0} --jobserver-auth={0}", arg)
}
/// Converts this [`Client`] into a helper thread to deal with a blocking
/// [`Client::acquire`] function a little more easily.
///
/// The fact that the [`Client::acquire`] isn't always the easiest to work
/// with. Typically you're using a jobserver to manage running other events
/// in parallel! This means that you need to either (a) wait for an existing
/// job to finish or (b) wait for a new token to become available.
///
/// Unfortunately the blocking in [`Client::acquire`] happens at the
/// implementation layer of jobservers. On Unix this requires a blocking
/// call to `read` and on Windows this requires one of the `WaitFor*`
/// functions. Both of these situations aren't the easiest to deal with:
///
/// * On Unix there's basically only one way to wake up a `read` early, and
/// that's through a signal. This is what the `make` implementation
/// itself uses, relying on `SIGCHLD` to wake up a blocking acquisition
/// of a new job token. Unfortunately nonblocking I/O is not an option
/// here, so it means that "waiting for one of two events" means that
/// the latter event must generate a signal! This is not always the case
/// on unix for all jobservers.
///
/// * On Windows you'd have to basically use the `WaitForMultipleObjects`
/// which means that you've got to canonicalize all your event sources
/// into a `HANDLE` which also isn't the easiest thing to do
/// unfortunately.
///
/// This function essentially attempts to ease these limitations by
/// converting this [`Client`] into a helper thread spawned into this
/// process. The application can then request that the helper thread
/// acquires tokens and the provided closure will be invoked for each token
/// acquired.
///
/// The intention is that this function can be used to translate the event
/// of a token acquisition into an arbitrary user-defined event.
///
/// # Arguments
///
/// This function will consume the [`Client`] provided to be transferred to
/// the helper thread that is spawned. Additionally a closure `f` is
/// provided to be invoked whenever a token is acquired.
///
/// This closure is only invoked after calls to
/// [`HelperThread::request_token`] have been made and a token itself has
/// been acquired. If an error happens while acquiring the token then
/// an error will be yielded to the closure as well.
///
/// # Return Value
///
/// This function will return an instance of the [`HelperThread`] structure
/// which is used to manage the helper thread associated with this client.
/// Through the [`HelperThread`] you'll request that tokens are acquired.
/// When acquired, the closure provided here is invoked.
///
/// When the [`HelperThread`] structure is returned it will be gracefully
/// torn down, and the calling thread will be blocked until the thread is
/// torn down (which should be prompt).
///
/// # Errors
///
/// This function may fail due to creation of the helper thread or
/// auxiliary I/O objects to manage the helper thread. In any of these
/// situations the error is propagated upwards.
///
/// # Platform-specific behavior
///
/// On Windows this function behaves pretty normally as expected, but on
/// Unix the implementation is... a little heinous. As mentioned above
/// we're forced into blocking I/O for token acquisition, namely a blocking
/// call to `read`. We must be able to unblock this, however, to tear down
/// the helper thread gracefully!
///
/// Essentially what happens is that we'll send a signal to the helper
/// thread spawned and rely on `EINTR` being returned to wake up the helper
/// thread. This involves installing a global `SIGUSR1` handler that does
/// nothing along with sending signals to that thread. This may cause
/// odd behavior in some applications, so it's recommended to review and
/// test thoroughly before using this.
pub fn into_helper_thread<F>(self, f: F) -> io::Result<HelperThread>
where
F: FnMut(io::Result<Acquired>) + Send + 'static,
{
let state = Arc::new(HelperState::default());
Ok(HelperThread {
inner: Some(imp::spawn_helper(self, state.clone(), Box::new(f))?),
state,
})
}
/// Blocks the current thread until a token is acquired.
///
/// This is the same as [`Client::acquire`], except that it doesn't return
/// an RAII helper. If successful the process will need to guarantee that
/// [`Client::release_raw`] is called in the future.
pub fn acquire_raw(&self) -> io::Result<()> {
self.inner.acquire()?;
Ok(())
}
/// Releases a jobserver token back to the original jobserver.
///
/// This is intended to be paired with [`Client::acquire_raw`] if it was
/// called, but in some situations it could also be called to relinquish a
/// process's implicit token temporarily which is then re-acquired later.
pub fn release_raw(&self) -> io::Result<()> {
self.inner.release(None)?;
Ok(())
}
}
impl Drop for Acquired {
fn drop(&mut self) {
if !self.disabled {
drop(self.client.release(Some(&self.data)));
}
}
}
/// Structure returned from [`Client::into_helper_thread`] to manage the lifetime
/// of the helper thread returned, see those associated docs for more info.
#[derive(Debug)]
pub struct HelperThread {
inner: Option<imp::Helper>,
state: Arc<HelperState>,
}
impl HelperThread {
/// Request that the helper thread acquires a token, eventually calling the
/// original closure with a token when it's available.
///
/// For more information, see the docs on [`Client::into_helper_thread`].
pub fn request_token(&self) {
// Indicate that there's one more request for a token and then wake up
// the helper thread if it's sleeping.
self.state.lock().requests += 1;
self.state.cvar.notify_one();
}
}
impl Drop for HelperThread {
fn drop(&mut self) {
// Flag that the producer half is done so the helper thread should exit
// quickly if it's waiting. Wake it up if it's actually waiting
self.state.lock().producer_done = true;
self.state.cvar.notify_one();
// ... and afterwards perform any thread cleanup logic
self.inner.take().unwrap().join();
}
}
impl HelperState {
fn lock(&self) -> MutexGuard<'_, HelperInner> {
self.lock.lock().unwrap_or_else(|e| e.into_inner())
}
/// Executes `f` for each request for a token, where `f` is expected to
/// block and then provide the original closure with a token once it's
/// acquired.
///
/// This is an infinite loop until the helper thread is dropped, at which
/// point everything should get interrupted.
fn for_each_request(&self, mut f: impl FnMut(&HelperState)) {
let mut lock = self.lock();
// We only execute while we could receive requests, but as soon as
// that's `false` we're out of here.
while !lock.producer_done {
// If no one's requested a token then we wait for someone to
// request a token.
if lock.requests == 0 {
lock = self.cvar.wait(lock).unwrap_or_else(|e| e.into_inner());
continue;
}
// Consume the request for a token, and then actually acquire a
// token after unlocking our lock (not that acquisition happens in
// `f`). This ensures that we don't actually hold the lock if we
// wait for a long time for a token.
lock.requests -= 1;
drop(lock);
f(self);
lock = self.lock();
}
lock.consumer_done = true;
self.cvar.notify_one();
}
}
/// Finds and returns the value of `--jobserver-auth=<VALUE>` in the given
/// environment variable.
///
/// Precedence rules:
///
/// * The last instance wins [^1].
/// * `--jobserver-fds=` as a fallback when no `--jobserver-auth=` is present [^2].
///
/// [^1]: See ["GNU `make` manual: Sharing Job Slots with GNU `make`"](https://www.gnu.org/software/make/manual/make.html#Job-Slots)
/// _"Be aware that the `MAKEFLAGS` variable may contain multiple instances of
/// the `--jobserver-auth=` option. Only the last instance is relevant."_
///
/// [^2]: Refer to [the release announcement](https://git.savannah.gnu.org/cgit/make.git/tree/NEWS?h=4.2#n31)
/// of GNU Make 4.2, which states that `--jobserver-fds` was initially an
/// internal-only flag and was later renamed to `--jobserver-auth`.
fn find_jobserver_auth(var: &str) -> Option<&str> {
["--jobserver-auth=", "--jobserver-fds="]
.iter()
.find_map(|&arg| var.rsplit_once(arg).map(|(_, s)| s))
.and_then(|s| s.split(' ').next())
}
#[cfg(test)]
mod test {
use super::*;
pub(super) fn run_named_fifo_try_acquire_tests(client: &Client) {
assert!(client.try_acquire().unwrap().is_none());
client.release_raw().unwrap();
let acquired = client.try_acquire().unwrap().unwrap();
assert!(client.try_acquire().unwrap().is_none());
drop(acquired);
client.try_acquire().unwrap().unwrap();
}
#[cfg(windows)]
#[test]
fn test_try_acquire() {
let client = Client::new(0).unwrap();
run_named_fifo_try_acquire_tests(&client);
}
#[test]
fn no_helper_deadlock() {
let x = crate::Client::new(32).unwrap();
let _y = x.clone();
std::mem::drop(x.into_helper_thread(|_| {}).unwrap());
}
#[test]
fn test_find_jobserver_auth() {
let cases = [
("", None),
("-j2", None),
("-j2 --jobserver-auth=3,4", Some("3,4")),
("--jobserver-auth=3,4 -j2", Some("3,4")),
("--jobserver-auth=3,4", Some("3,4")),
("--jobserver-auth=fifo:/myfifo", Some("fifo:/myfifo")),
("--jobserver-auth=", Some("")),
("--jobserver-auth", None),
("--jobserver-fds=3,4", Some("3,4")),
("--jobserver-fds=fifo:/myfifo", Some("fifo:/myfifo")),
("--jobserver-fds=", Some("")),
("--jobserver-fds", None),
(
"--jobserver-auth=auth-a --jobserver-auth=auth-b",
Some("auth-b"),
),
(
"--jobserver-auth=auth-b --jobserver-auth=auth-a",
Some("auth-a"),
),
("--jobserver-fds=fds-a --jobserver-fds=fds-b", Some("fds-b")),
("--jobserver-fds=fds-b --jobserver-fds=fds-a", Some("fds-a")),
(
"--jobserver-auth=auth-a --jobserver-fds=fds-a --jobserver-auth=auth-b",
Some("auth-b"),
),
(
"--jobserver-fds=fds-a --jobserver-auth=auth-a --jobserver-fds=fds-b",
Some("auth-a"),
),
];
for (var, expected) in cases {
let actual = find_jobserver_auth(var);
assert_eq!(
actual, expected,
"expect {expected:?}, got {actual:?}, input `{var:?}`"
);
}
}
}

634
vendor/jobserver/src/unix.rs vendored Normal file
View File

@@ -0,0 +1,634 @@
use libc::c_int;
use crate::FromEnvErrorInner;
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Write};
use std::mem;
use std::mem::MaybeUninit;
use std::os::unix::prelude::*;
use std::path::Path;
use std::process::Command;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use std::thread::{self, Builder, JoinHandle};
use std::time::Duration;
#[derive(Debug)]
/// This preserves the `--jobserver-auth` type at creation time,
/// so auth type will be passed down to and inherit from sub-Make processes correctly.
///
/// See <https://github.com/rust-lang/jobserver-rs/issues/99> for details.
enum ClientCreationArg {
Fds { read: c_int, write: c_int },
Fifo(Box<Path>),
}
#[derive(Debug)]
pub struct Client {
read: File,
write: File,
creation_arg: ClientCreationArg,
/// It is set to `None` if the pipe is shared with other processes, so it
/// cannot support non-blocking mode.
///
/// If it is set to `Some`, then it can only go from
/// `Some(false)` -> `Some(true)` but not the other way around,
/// since that could cause a race condition.
is_non_blocking: Option<AtomicBool>,
}
#[derive(Debug)]
pub struct Acquired {
byte: u8,
}
impl Client {
pub fn new(mut limit: usize) -> io::Result<Client> {
let client = unsafe { Client::mk()? };
// I don't think the character written here matters, but I could be
// wrong!
const BUFFER: [u8; 128] = [b'|'; 128];
let mut write = &client.write;
set_nonblocking(write.as_raw_fd(), true)?;
while limit > 0 {
let n = limit.min(BUFFER.len());
write.write_all(&BUFFER[..n])?;
limit -= n;
}
set_nonblocking(write.as_raw_fd(), false)?;
Ok(client)
}
unsafe fn mk() -> io::Result<Client> {
let mut pipes = [0; 2];
// Atomically-create-with-cloexec on Linux.
#[cfg(target_os = "linux")]
if libc::pipe2(pipes.as_mut_ptr(), libc::O_CLOEXEC) == -1 {
return Err(io::Error::last_os_error());
}
#[cfg(not(target_os = "linux"))]
{
cvt(libc::pipe(pipes.as_mut_ptr()))?;
drop(set_cloexec(pipes[0], true));
drop(set_cloexec(pipes[1], true));
}
Ok(Client::from_fds(pipes[0], pipes[1]))
}
pub(crate) unsafe fn open(s: &str, check_pipe: bool) -> Result<Client, FromEnvErrorInner> {
if let Some(client) = Self::from_fifo(s)? {
return Ok(client);
}
if let Some(client) = Self::from_pipe(s, check_pipe)? {
return Ok(client);
}
Err(FromEnvErrorInner::CannotParse(format!(
"expected `fifo:PATH` or `R,W`, found `{s}`"
)))
}
/// `--jobserver-auth=fifo:PATH`
fn from_fifo(s: &str) -> Result<Option<Client>, FromEnvErrorInner> {
let mut parts = s.splitn(2, ':');
if parts.next().unwrap() != "fifo" {
return Ok(None);
}
let path_str = parts.next().ok_or_else(|| {
FromEnvErrorInner::CannotParse("expected a path after `fifo:`".to_string())
})?;
let path = Path::new(path_str);
let open_file = || {
// Opening with read write is necessary, since opening with
// read-only or write-only could block the thread until another
// thread opens it with write-only or read-only (or RDWR)
// correspondingly.
OpenOptions::new()
.read(true)
.write(true)
.open(path)
.map_err(|err| FromEnvErrorInner::CannotOpenPath(path_str.to_string(), err))
};
Ok(Some(Client {
read: open_file()?,
write: open_file()?,
creation_arg: ClientCreationArg::Fifo(path.into()),
is_non_blocking: Some(AtomicBool::new(false)),
}))
}
/// `--jobserver-auth=R,W`
unsafe fn from_pipe(s: &str, check_pipe: bool) -> Result<Option<Client>, FromEnvErrorInner> {
let mut parts = s.splitn(2, ',');
let read = parts.next().unwrap();
let write = match parts.next() {
Some(w) => w,
None => return Ok(None),
};
let read = read
.parse()
.map_err(|e| FromEnvErrorInner::CannotParse(format!("cannot parse `read` fd: {e}")))?;
let write = write
.parse()
.map_err(|e| FromEnvErrorInner::CannotParse(format!("cannot parse `write` fd: {e}")))?;
// If either or both of these file descriptors are negative,
// it means the jobserver is disabled for this process.
if read < 0 {
return Err(FromEnvErrorInner::NegativeFd(read));
}
if write < 0 {
return Err(FromEnvErrorInner::NegativeFd(write));
}
let creation_arg = ClientCreationArg::Fds { read, write };
// Ok so we've got two integers that look like file descriptors, but
// for extra sanity checking let's see if they actually look like
// valid files and instances of a pipe if feature enabled before we
// return the client.
//
// If we're called from `make` *without* the leading + on our rule
// then we'll have `MAKEFLAGS` env vars but won't actually have
// access to the file descriptors.
//
// `NotAPipe` is a worse error, return it if it's reported for any of the two fds.
match (fd_check(read, check_pipe), fd_check(write, check_pipe)) {
(read_err @ Err(FromEnvErrorInner::NotAPipe(..)), _) => read_err?,
(_, write_err @ Err(FromEnvErrorInner::NotAPipe(..))) => write_err?,
(read_err, write_err) => {
read_err?;
write_err?;
// Optimization: Try converting it to a fifo by using /dev/fd
//
// On linux, opening `/dev/fd/$fd` returns a fd with a new file description,
// so we can set `O_NONBLOCK` on it without affecting other processes.
//
// On macOS, opening `/dev/fd/$fd` seems to be the same as `File::try_clone`.
//
// I tested this on macOS 14 and Linux 6.5.13
#[cfg(target_os = "linux")]
if let (Ok(read), Ok(write)) = (
File::open(format!("/dev/fd/{}", read)),
OpenOptions::new()
.write(true)
.open(format!("/dev/fd/{}", write)),
) {
return Ok(Some(Client {
read,
write,
creation_arg,
is_non_blocking: Some(AtomicBool::new(false)),
}));
}
}
}
Ok(Some(Client {
read: clone_fd_and_set_cloexec(read)?,
write: clone_fd_and_set_cloexec(write)?,
creation_arg,
is_non_blocking: None,
}))
}
unsafe fn from_fds(read: c_int, write: c_int) -> Client {
Client {
read: File::from_raw_fd(read),
write: File::from_raw_fd(write),
creation_arg: ClientCreationArg::Fds { read, write },
is_non_blocking: None,
}
}
pub fn acquire(&self) -> io::Result<Acquired> {
// Ignore interrupts and keep trying if that happens
loop {
if let Some(token) = self.acquire_allow_interrupts()? {
return Ok(token);
}
}
}
/// Block waiting for a token, returning `None` if we're interrupted with
/// EINTR.
fn acquire_allow_interrupts(&self) -> io::Result<Option<Acquired>> {
// We don't actually know if the file descriptor here is set in
// blocking or nonblocking mode. AFAIK all released versions of
// `make` use blocking fds for the jobserver, but the unreleased
// version of `make` doesn't. In the unreleased version jobserver
// fds are set to nonblocking and combined with `pselect`
// internally.
//
// Here we try to be compatible with both strategies. We optimistically
// try to read from the file descriptor which then may block, return
// a token or indicate that polling is needed.
// Blocking reads (if possible) allows the kernel to be more selective
// about which readers to wake up when a token is written to the pipe.
//
// We use `poll` here to block this thread waiting for read
// readiness, and then afterwards we perform the `read` itself. If
// the `read` returns that it would block then we start over and try
// again.
//
// Also note that we explicitly don't handle EINTR here. That's used
// to shut us down, so we otherwise punt all errors upwards.
unsafe {
let mut fd: libc::pollfd = mem::zeroed();
let mut read = &self.read;
fd.fd = read.as_raw_fd();
fd.events = libc::POLLIN;
loop {
let mut buf = [0];
match read.read(&mut buf) {
Ok(1) => return Ok(Some(Acquired { byte: buf[0] })),
Ok(_) => {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"early EOF on jobserver pipe",
));
}
Err(e) => match e.kind() {
io::ErrorKind::WouldBlock => { /* fall through to polling */ }
io::ErrorKind::Interrupted => return Ok(None),
_ => return Err(e),
},
}
loop {
fd.revents = 0;
if libc::poll(&mut fd, 1, -1) == -1 {
let e = io::Error::last_os_error();
return match e.kind() {
io::ErrorKind::Interrupted => Ok(None),
_ => Err(e),
};
}
if fd.revents != 0 {
break;
}
}
}
}
}
pub fn try_acquire(&self) -> io::Result<Option<Acquired>> {
let mut buf = [0];
let mut fifo = &self.read;
if let Some(is_non_blocking) = self.is_non_blocking.as_ref() {
if !is_non_blocking.load(Ordering::Relaxed) {
set_nonblocking(fifo.as_raw_fd(), true)?;
is_non_blocking.store(true, Ordering::Relaxed);
}
} else {
return Err(io::ErrorKind::Unsupported.into());
}
loop {
match fifo.read(&mut buf) {
Ok(1) => break Ok(Some(Acquired { byte: buf[0] })),
Ok(_) => {
break Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"early EOF on jobserver pipe",
))
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => break Ok(None),
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(err) => break Err(err),
}
}
}
pub fn release(&self, data: Option<&Acquired>) -> io::Result<()> {
// Note that the fd may be nonblocking but we're going to go ahead
// and assume that the writes here are always nonblocking (we can
// always quickly release a token). If that turns out to not be the
// case we'll get an error anyway!
let byte = data.map(|d| d.byte).unwrap_or(b'+');
match (&self.write).write(&[byte])? {
1 => Ok(()),
_ => Err(io::Error::new(
io::ErrorKind::Other,
"failed to write token back to jobserver",
)),
}
}
pub fn string_arg(&self) -> String {
match &self.creation_arg {
ClientCreationArg::Fifo(path) => format!("fifo:{}", path.display()),
ClientCreationArg::Fds { read, write } => format!("{},{}", read, write),
}
}
pub fn available(&self) -> io::Result<usize> {
let mut len = MaybeUninit::<c_int>::uninit();
cvt(unsafe { libc::ioctl(self.read.as_raw_fd(), libc::FIONREAD, len.as_mut_ptr()) })?;
Ok(unsafe { len.assume_init() } as usize)
}
pub fn configure(&self, cmd: &mut Command) {
if matches!(self.creation_arg, ClientCreationArg::Fifo { .. }) {
// We `File::open`ed it when inheriting from environment,
// so no need to set cloexec for fifo.
return;
}
// Here we basically just want to say that in the child process
// we'll configure the read/write file descriptors to *not* be
// cloexec, so they're inherited across the exec and specified as
// integers through `string_arg` above.
let read = self.read.as_raw_fd();
let write = self.write.as_raw_fd();
unsafe {
cmd.pre_exec(move || {
set_cloexec(read, false)?;
set_cloexec(write, false)?;
Ok(())
});
}
}
}
#[derive(Debug)]
pub struct Helper {
thread: JoinHandle<()>,
state: Arc<super::HelperState>,
}
pub(crate) fn spawn_helper(
client: crate::Client,
state: Arc<super::HelperState>,
mut f: Box<dyn FnMut(io::Result<crate::Acquired>) + Send>,
) -> io::Result<Helper> {
#[cfg(not(miri))]
{
use std::sync::Once;
static USR1_INIT: Once = Once::new();
let mut err = None;
USR1_INIT.call_once(|| unsafe {
let mut new: libc::sigaction = mem::zeroed();
new.sa_sigaction = sigusr1_handler as usize;
new.sa_flags = libc::SA_SIGINFO as _;
if libc::sigaction(libc::SIGUSR1, &new, std::ptr::null_mut()) != 0 {
err = Some(io::Error::last_os_error());
}
});
if let Some(e) = err.take() {
return Err(e);
}
}
let state2 = state.clone();
let thread = Builder::new().spawn(move || {
state2.for_each_request(|helper| loop {
match client.inner.acquire_allow_interrupts() {
Ok(Some(data)) => {
break f(Ok(crate::Acquired {
client: client.inner.clone(),
data,
disabled: false,
}));
}
Err(e) => break f(Err(e)),
Ok(None) if helper.lock().producer_done => break,
Ok(None) => {}
}
});
})?;
Ok(Helper { thread, state })
}
impl Helper {
pub fn join(self) {
let dur = Duration::from_millis(10);
let mut state = self.state.lock();
debug_assert!(state.producer_done);
// We need to join our helper thread, and it could be blocked in one
// of two locations. First is the wait for a request, but the
// initial drop of `HelperState` will take care of that. Otherwise
// it may be blocked in `client.acquire()`. We actually have no way
// of interrupting that, so resort to `pthread_kill` as a fallback.
// This signal should interrupt any blocking `read` call with
// `io::ErrorKind::Interrupt` and cause the thread to cleanly exit.
//
// Note that we don't do this forever though since there's a chance
// of bugs, so only do this opportunistically to make a best effort
// at clearing ourselves up.
for _ in 0..100 {
if state.consumer_done {
break;
}
#[cfg(not(miri))]
unsafe {
// Ignore the return value here of `pthread_kill`,
// apparently on OSX if you kill a dead thread it will
// return an error, but on other platforms it may not. In
// that sense we don't actually know if this will succeed or
// not!
libc::pthread_kill(self.thread.as_pthread_t() as _, libc::SIGUSR1);
}
state = self
.state
.cvar
.wait_timeout(state, dur)
.unwrap_or_else(|e| e.into_inner())
.0;
thread::yield_now(); // we really want the other thread to run
}
// If we managed to actually see the consumer get done, then we can
// definitely wait for the thread. Otherwise it's... off in the ether
// I guess?
if state.consumer_done {
drop(self.thread.join());
}
}
}
unsafe fn fcntl_check(fd: c_int) -> Result<(), FromEnvErrorInner> {
match libc::fcntl(fd, libc::F_GETFD) {
-1 => Err(FromEnvErrorInner::CannotOpenFd(
fd,
io::Error::last_os_error(),
)),
_ => Ok(()),
}
}
unsafe fn fd_check(fd: c_int, check_pipe: bool) -> Result<(), FromEnvErrorInner> {
if check_pipe {
let mut stat = mem::zeroed();
if libc::fstat(fd, &mut stat) == -1 {
let last_os_error = io::Error::last_os_error();
fcntl_check(fd)?;
Err(FromEnvErrorInner::NotAPipe(fd, Some(last_os_error)))
} else {
// On android arm and i686 mode_t is u16 and st_mode is u32,
// this generates a type mismatch when S_IFIFO (declared as mode_t)
// is used in operations with st_mode, so we use this workaround
// to get the value of S_IFIFO with the same type of st_mode.
#[allow(unused_assignments)]
let mut s_ififo = stat.st_mode;
s_ififo = libc::S_IFIFO as _;
if stat.st_mode & s_ififo == s_ififo {
return Ok(());
}
Err(FromEnvErrorInner::NotAPipe(fd, None))
}
} else {
fcntl_check(fd)
}
}
fn clone_fd_and_set_cloexec(fd: c_int) -> Result<File, FromEnvErrorInner> {
// Safety: fd is a valid fd and it remains open until returns
unsafe { BorrowedFd::borrow_raw(fd) }
.try_clone_to_owned()
.map(File::from)
.map_err(|err| FromEnvErrorInner::CannotOpenFd(fd, err))
}
fn set_cloexec(fd: c_int, set: bool) -> io::Result<()> {
unsafe {
let previous = cvt(libc::fcntl(fd, libc::F_GETFD))?;
let new = if set {
previous | libc::FD_CLOEXEC
} else {
previous & !libc::FD_CLOEXEC
};
if new != previous {
cvt(libc::fcntl(fd, libc::F_SETFD, new))?;
}
Ok(())
}
}
fn set_nonblocking(fd: c_int, set: bool) -> io::Result<()> {
let status_flag = if set { libc::O_NONBLOCK } else { 0 };
unsafe {
cvt(libc::fcntl(fd, libc::F_SETFL, status_flag))?;
}
Ok(())
}
fn cvt(t: c_int) -> io::Result<c_int> {
if t == -1 {
Err(io::Error::last_os_error())
} else {
Ok(t)
}
}
#[cfg(not(miri))]
extern "C" fn sigusr1_handler(
_signum: c_int,
_info: *mut libc::siginfo_t,
_ptr: *mut libc::c_void,
) {
// nothing to do
}
#[cfg(test)]
mod test {
use super::Client as ClientImp;
use crate::{test::run_named_fifo_try_acquire_tests, Client};
use std::{fs::File, io::Write, os::unix::io::AsRawFd, sync::Arc};
fn from_imp_client(imp: ClientImp) -> Client {
Client {
inner: Arc::new(imp),
}
}
fn new_client_from_fifo() -> (Client, String) {
let file = tempfile::NamedTempFile::new().unwrap();
let fifo_path = file.path().to_owned();
file.close().unwrap(); // Remove the NamedTempFile to create fifo
nix::unistd::mkfifo(&fifo_path, nix::sys::stat::Mode::S_IRWXU).unwrap();
let arg = format!("fifo:{}", fifo_path.to_str().unwrap());
(
ClientImp::from_fifo(&arg)
.unwrap()
.map(from_imp_client)
.unwrap(),
arg,
)
}
fn new_client_from_pipe() -> (Client, String) {
let (read, write) = nix::unistd::pipe().unwrap();
let read = File::from(read);
let mut write = File::from(write);
write.write_all(b"1").unwrap();
let arg = format!("{},{}", read.as_raw_fd(), write.as_raw_fd());
(
unsafe { ClientImp::from_pipe(&arg, true) }
.unwrap()
.map(from_imp_client)
.unwrap(),
arg,
)
}
#[test]
fn test_try_acquire_named_fifo() {
run_named_fifo_try_acquire_tests(&new_client_from_fifo().0);
}
#[test]
fn test_try_acquire_annoymous_pipe_linux_specific_optimization() {
#[cfg(not(target_os = "linux"))]
assert_eq!(
new_client_from_pipe().0.try_acquire().unwrap_err().kind(),
std::io::ErrorKind::Unsupported
);
#[cfg(target_os = "linux")]
{
let client = new_client_from_pipe().0;
client.acquire().unwrap().drop_without_releasing();
run_named_fifo_try_acquire_tests(&client);
}
}
#[test]
fn test_string_arg() {
let (client, arg) = new_client_from_fifo();
assert_eq!(client.inner.string_arg(), arg);
let (client, arg) = new_client_from_pipe();
assert_eq!(client.inner.string_arg(), arg);
}
}

106
vendor/jobserver/src/wasm.rs vendored Normal file
View File

@@ -0,0 +1,106 @@
use crate::FromEnvErrorInner;
use std::io;
use std::process::Command;
use std::sync::{Arc, Condvar, Mutex};
use std::thread::{Builder, JoinHandle};
#[derive(Debug)]
pub struct Client {
inner: Arc<Inner>,
}
#[derive(Debug)]
struct Inner {
count: Mutex<usize>,
cvar: Condvar,
}
#[derive(Debug)]
pub struct Acquired(());
impl Client {
pub fn new(limit: usize) -> io::Result<Client> {
Ok(Client {
inner: Arc::new(Inner {
count: Mutex::new(limit),
cvar: Condvar::new(),
}),
})
}
pub(crate) unsafe fn open(_s: &str, _check_pipe: bool) -> Result<Client, FromEnvErrorInner> {
Err(FromEnvErrorInner::Unsupported)
}
pub fn acquire(&self) -> io::Result<Acquired> {
let mut lock = self.inner.count.lock().unwrap_or_else(|e| e.into_inner());
while *lock == 0 {
lock = self
.inner
.cvar
.wait(lock)
.unwrap_or_else(|e| e.into_inner());
}
*lock -= 1;
Ok(Acquired(()))
}
pub fn try_acquire(&self) -> io::Result<Option<Acquired>> {
let mut lock = self.inner.count.lock().unwrap_or_else(|e| e.into_inner());
if *lock == 0 {
Ok(None)
} else {
*lock -= 1;
Ok(Some(Acquired(())))
}
}
pub fn release(&self, _data: Option<&Acquired>) -> io::Result<()> {
let mut lock = self.inner.count.lock().unwrap_or_else(|e| e.into_inner());
*lock += 1;
drop(lock);
self.inner.cvar.notify_one();
Ok(())
}
pub fn string_arg(&self) -> String {
panic!(
"On this platform there is no cross process jobserver support,
so Client::configure is not supported."
);
}
pub fn available(&self) -> io::Result<usize> {
let lock = self.inner.count.lock().unwrap_or_else(|e| e.into_inner());
Ok(*lock)
}
pub fn configure(&self, _cmd: &mut Command) {
unreachable!();
}
}
#[derive(Debug)]
pub struct Helper {
thread: JoinHandle<()>,
}
pub(crate) fn spawn_helper(
client: crate::Client,
state: Arc<super::HelperState>,
mut f: Box<dyn FnMut(io::Result<crate::Acquired>) + Send>,
) -> io::Result<Helper> {
let thread = Builder::new().spawn(move || {
state.for_each_request(|_| f(client.acquire()));
})?;
Ok(Helper { thread })
}
impl Helper {
pub fn join(self) {
// TODO: this is not correct if the thread is blocked in
// `client.acquire()`.
drop(self.thread.join());
}
}

269
vendor/jobserver/src/windows.rs vendored Normal file
View File

@@ -0,0 +1,269 @@
use crate::FromEnvErrorInner;
use std::ffi::CString;
use std::io;
use std::process::Command;
use std::ptr;
use std::sync::Arc;
use std::thread::{Builder, JoinHandle};
#[derive(Debug)]
pub struct Client {
sem: Handle,
name: String,
}
#[derive(Debug)]
pub struct Acquired;
#[allow(clippy::upper_case_acronyms)]
type BOOL = i32;
#[allow(clippy::upper_case_acronyms)]
type DWORD = u32;
#[allow(clippy::upper_case_acronyms)]
type HANDLE = *mut u8;
#[allow(clippy::upper_case_acronyms)]
type LONG = i32;
const ERROR_ALREADY_EXISTS: DWORD = 183;
const FALSE: BOOL = 0;
const INFINITE: DWORD = 0xffffffff;
const SEMAPHORE_MODIFY_STATE: DWORD = 0x2;
const SYNCHRONIZE: DWORD = 0x00100000;
const TRUE: BOOL = 1;
const WAIT_ABANDONED: DWORD = 128u32;
const WAIT_FAILED: DWORD = 4294967295u32;
const WAIT_OBJECT_0: DWORD = 0u32;
const WAIT_TIMEOUT: DWORD = 258u32;
#[link(name = "kernel32")]
extern "system" {
fn CloseHandle(handle: HANDLE) -> BOOL;
fn SetEvent(hEvent: HANDLE) -> BOOL;
fn WaitForMultipleObjects(
ncount: DWORD,
lpHandles: *const HANDLE,
bWaitAll: BOOL,
dwMilliseconds: DWORD,
) -> DWORD;
fn CreateEventA(
lpEventAttributes: *mut u8,
bManualReset: BOOL,
bInitialState: BOOL,
lpName: *const i8,
) -> HANDLE;
fn ReleaseSemaphore(
hSemaphore: HANDLE,
lReleaseCount: LONG,
lpPreviousCount: *mut LONG,
) -> BOOL;
fn CreateSemaphoreA(
lpEventAttributes: *mut u8,
lInitialCount: LONG,
lMaximumCount: LONG,
lpName: *const i8,
) -> HANDLE;
fn OpenSemaphoreA(dwDesiredAccess: DWORD, bInheritHandle: BOOL, lpName: *const i8) -> HANDLE;
fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) -> DWORD;
}
impl Client {
pub fn new(limit: usize) -> io::Result<Client> {
// Try a bunch of random semaphore names until we get a unique one,
// but don't try for too long.
//
// Note that `limit == 0` is a valid argument above but Windows
// won't let us create a semaphore with 0 slots available to it. Get
// `limit == 0` working by creating a semaphore instead with one
// slot and then immediately acquire it (without ever releaseing it
// back).
for _ in 0..100 {
let bytes = getrandom::u32()?;
let mut name = format!("__rust_jobserver_semaphore_{}\0", bytes);
unsafe {
let create_limit = if limit == 0 { 1 } else { limit };
let r = CreateSemaphoreA(
ptr::null_mut(),
create_limit as LONG,
create_limit as LONG,
name.as_ptr() as *const _,
);
if r.is_null() {
return Err(io::Error::last_os_error());
}
let handle = Handle(r);
let err = io::Error::last_os_error();
if err.raw_os_error() == Some(ERROR_ALREADY_EXISTS as i32) {
continue;
}
name.pop(); // chop off the trailing nul
let client = Client { sem: handle, name };
if create_limit != limit {
client.acquire()?;
}
return Ok(client);
}
}
Err(io::Error::new(
io::ErrorKind::Other,
"failed to find a unique name for a semaphore",
))
}
pub(crate) unsafe fn open(s: &str, _check_pipe: bool) -> Result<Client, FromEnvErrorInner> {
let name = match CString::new(s) {
Ok(s) => s,
Err(e) => return Err(FromEnvErrorInner::CannotParse(e.to_string())),
};
let sem = OpenSemaphoreA(SYNCHRONIZE | SEMAPHORE_MODIFY_STATE, FALSE, name.as_ptr());
if sem.is_null() {
Err(FromEnvErrorInner::CannotOpenPath(
s.to_string(),
io::Error::last_os_error(),
))
} else {
Ok(Client {
sem: Handle(sem),
name: s.to_string(),
})
}
}
pub fn acquire(&self) -> io::Result<Acquired> {
unsafe {
let r = WaitForSingleObject(self.sem.0, INFINITE);
if r == WAIT_OBJECT_0 {
Ok(Acquired)
} else {
Err(io::Error::last_os_error())
}
}
}
pub fn try_acquire(&self) -> io::Result<Option<Acquired>> {
match unsafe { WaitForSingleObject(self.sem.0, 0) } {
WAIT_OBJECT_0 => Ok(Some(Acquired)),
WAIT_TIMEOUT => Ok(None),
WAIT_FAILED => Err(io::Error::last_os_error()),
// We believe this should be impossible for a semaphore, but still
// check the error code just in case it happens.
WAIT_ABANDONED => Err(io::Error::new(
io::ErrorKind::Other,
"Wait on jobserver semaphore returned WAIT_ABANDONED",
)),
_ => unreachable!("Unexpected return value from WaitForSingleObject"),
}
}
pub fn release(&self, _data: Option<&Acquired>) -> io::Result<()> {
unsafe {
let r = ReleaseSemaphore(self.sem.0, 1, ptr::null_mut());
if r != 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
}
pub fn string_arg(&self) -> String {
self.name.clone()
}
pub fn available(&self) -> io::Result<usize> {
// Can't read value of a semaphore on Windows, so
// try to acquire without sleeping, since we can find out the
// old value on release. If acquisiton fails, then available is 0.
unsafe {
let r = WaitForSingleObject(self.sem.0, 0);
if r != WAIT_OBJECT_0 {
Ok(0)
} else {
let mut prev: LONG = 0;
let r = ReleaseSemaphore(self.sem.0, 1, &mut prev);
if r != 0 {
Ok(prev as usize + 1)
} else {
Err(io::Error::last_os_error())
}
}
}
}
pub fn configure(&self, _cmd: &mut Command) {
// nothing to do here, we gave the name of our semaphore to the
// child above
}
}
#[derive(Debug)]
struct Handle(HANDLE);
// HANDLE is a raw ptr, but we're send/sync
unsafe impl Sync for Handle {}
unsafe impl Send for Handle {}
impl Drop for Handle {
fn drop(&mut self) {
unsafe {
CloseHandle(self.0);
}
}
}
#[derive(Debug)]
pub struct Helper {
event: Arc<Handle>,
thread: JoinHandle<()>,
}
pub(crate) fn spawn_helper(
client: crate::Client,
state: Arc<super::HelperState>,
mut f: Box<dyn FnMut(io::Result<crate::Acquired>) + Send>,
) -> io::Result<Helper> {
let event = unsafe {
let r = CreateEventA(ptr::null_mut(), TRUE, FALSE, ptr::null());
if r.is_null() {
return Err(io::Error::last_os_error());
} else {
Handle(r)
}
};
let event = Arc::new(event);
let event2 = event.clone();
let thread = Builder::new().spawn(move || {
let objects = [event2.0, client.inner.sem.0];
state.for_each_request(|_| {
const WAIT_OBJECT_1: u32 = WAIT_OBJECT_0 + 1;
match unsafe { WaitForMultipleObjects(2, objects.as_ptr(), FALSE, INFINITE) } {
WAIT_OBJECT_0 => {}
WAIT_OBJECT_1 => f(Ok(crate::Acquired {
client: client.inner.clone(),
data: Acquired,
disabled: false,
})),
_ => f(Err(io::Error::last_os_error())),
}
});
})?;
Ok(Helper { thread, event })
}
impl Helper {
pub fn join(self) {
// Unlike unix this logic is much easier. If our thread was blocked
// in waiting for requests it should already be woken up and
// exiting. Otherwise it's waiting for a token, so we wake it up
// with a different event that it's also waiting on here. After
// these two we should be guaranteed the thread is on its way out,
// so we can safely `join`.
let r = unsafe { SetEvent(self.event.0) };
if r == 0 {
panic!("failed to set event: {}", io::Error::last_os_error());
}
drop(self.thread.join());
}
}

View File

@@ -0,0 +1,59 @@
use std::env;
use std::io::prelude::*;
use std::io::BufReader;
use std::process::{Command, Stdio};
use std::sync::mpsc;
use std::thread;
use jobserver::Client;
macro_rules! t {
($e:expr) => {
match $e {
Ok(e) => e,
Err(e) => panic!("{} failed with {}", stringify!($e), e),
}
};
}
fn main() {
if env::var("I_AM_THE_CLIENT").is_ok() {
client();
} else {
server();
}
}
fn server() {
let me = t!(env::current_exe());
let client = t!(Client::new(1));
let mut cmd = Command::new(me);
cmd.env("I_AM_THE_CLIENT", "1").stdout(Stdio::piped());
client.configure(&mut cmd);
let acq = client.acquire().unwrap();
let mut child = t!(cmd.spawn());
let stdout = child.stdout.take().unwrap();
let (tx, rx) = mpsc::channel();
let t = thread::spawn(move || {
for line in BufReader::new(stdout).lines() {
tx.send(t!(line)).unwrap();
}
});
for _ in 0..100 {
assert!(rx.try_recv().is_err());
}
drop(acq);
assert_eq!(rx.recv().unwrap(), "hello!");
t.join().unwrap();
assert!(rx.recv().is_err());
client.acquire().unwrap();
}
fn client() {
let client = unsafe { Client::from_env().unwrap() };
let acq = client.acquire().unwrap();
println!("hello!");
drop(acq);
}

192
vendor/jobserver/tests/client.rs vendored Normal file
View File

@@ -0,0 +1,192 @@
use std::env;
use std::fs::File;
use std::io::Write;
use std::process::Command;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
use std::sync::Arc;
use std::thread;
use jobserver::Client;
macro_rules! t {
($e:expr) => {
match $e {
Ok(e) => e,
Err(e) => panic!("{} failed with {}", stringify!($e), e),
}
};
}
struct Test {
name: &'static str,
f: &'static (dyn Fn() + Send + Sync),
make_args: &'static [&'static str],
rule: &'static (dyn Fn(&str) -> String + Send + Sync),
}
const TESTS: &[Test] = &[
Test {
name: "no j args",
make_args: &[],
rule: &|me| me.to_string(),
f: &|| {
assert!(unsafe { Client::from_env().is_none() });
},
},
Test {
name: "no j args with plus",
make_args: &[],
rule: &|me| format!("+{}", me),
f: &|| {
assert!(unsafe { Client::from_env().is_none() });
},
},
Test {
name: "j args with plus",
make_args: &["-j2"],
rule: &|me| format!("+{}", me),
f: &|| {
assert!(unsafe { Client::from_env().is_some() });
},
},
Test {
name: "acquire",
make_args: &["-j2"],
rule: &|me| format!("+{}", me),
f: &|| {
let c = unsafe { Client::from_env().unwrap() };
drop(c.acquire().unwrap());
drop(c.acquire().unwrap());
},
},
Test {
name: "acquire3",
make_args: &["-j3"],
rule: &|me| format!("+{}", me),
f: &|| {
let c = unsafe { Client::from_env().unwrap() };
let a = c.acquire().unwrap();
let b = c.acquire().unwrap();
drop((a, b));
},
},
Test {
name: "acquire blocks",
make_args: &["-j2"],
rule: &|me| format!("+{}", me),
f: &|| {
let c = unsafe { Client::from_env().unwrap() };
let a = c.acquire().unwrap();
let hit = Arc::new(AtomicBool::new(false));
let hit2 = hit.clone();
let (tx, rx) = mpsc::channel();
let t = thread::spawn(move || {
tx.send(()).unwrap();
let _b = c.acquire().unwrap();
hit2.store(true, Ordering::SeqCst);
});
rx.recv().unwrap();
assert!(!hit.load(Ordering::SeqCst));
drop(a);
t.join().unwrap();
assert!(hit.load(Ordering::SeqCst));
},
},
Test {
name: "acquire_raw",
make_args: &["-j2"],
rule: &|me| format!("+{}", me),
f: &|| {
let c = unsafe { Client::from_env().unwrap() };
c.acquire_raw().unwrap();
c.release_raw().unwrap();
},
},
];
fn main() {
if let Ok(test) = env::var("TEST_TO_RUN") {
return (TESTS.iter().find(|t| t.name == test).unwrap().f)();
}
let me = t!(env::current_exe());
let me = me.to_str().unwrap();
let filter = env::args().nth(1);
let join_handles = TESTS
.iter()
.filter(|test| match filter {
Some(ref s) => test.name.contains(s),
None => true,
})
.map(|test| {
let td = t!(tempfile::tempdir());
let makefile = format!(
"\
all: export TEST_TO_RUN={}
all:
\t{}
",
test.name,
(test.rule)(me)
);
t!(t!(File::create(td.path().join("Makefile"))).write_all(makefile.as_bytes()));
thread::spawn(move || {
let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string());
let mut cmd = Command::new(prog);
cmd.args(test.make_args);
cmd.current_dir(td.path());
(test, cmd.output().unwrap())
})
})
.collect::<Vec<_>>();
println!("\nrunning {} tests\n", join_handles.len());
let failures = join_handles
.into_iter()
.filter_map(|join_handle| {
let (test, output) = join_handle.join().unwrap();
if output.status.success() {
println!("test {} ... ok", test.name);
None
} else {
println!("test {} ... FAIL", test.name);
Some((test, output))
}
})
.collect::<Vec<_>>();
if failures.is_empty() {
println!("\ntest result: ok\n");
return;
}
println!("\n----------- failures");
for (test, output) in failures {
println!("test {}", test.name);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!("\texit status: {}", output.status);
if !stdout.is_empty() {
println!("\tstdout ===");
for line in stdout.lines() {
println!("\t\t{}", line);
}
}
if !stderr.is_empty() {
println!("\tstderr ===");
for line in stderr.lines() {
println!("\t\t{}", line);
}
}
}
std::process::exit(4);
}

76
vendor/jobserver/tests/helper.rs vendored Normal file
View File

@@ -0,0 +1,76 @@
use jobserver::Client;
use std::sync::atomic::*;
use std::sync::*;
macro_rules! t {
($e:expr) => {
match $e {
Ok(e) => e,
Err(e) => panic!("{} failed with {}", stringify!($e), e),
}
};
}
#[test]
fn helper_smoke() {
let client = t!(Client::new(1));
drop(client.clone().into_helper_thread(|_| ()).unwrap());
drop(client.clone().into_helper_thread(|_| ()).unwrap());
drop(client.clone().into_helper_thread(|_| ()).unwrap());
drop(client.clone().into_helper_thread(|_| ()).unwrap());
drop(client.clone().into_helper_thread(|_| ()).unwrap());
drop(client.into_helper_thread(|_| ()).unwrap());
}
#[test]
fn acquire() {
let (tx, rx) = mpsc::channel();
let client = t!(Client::new(1));
let helper = client
.into_helper_thread(move |a| drop(tx.send(a)))
.unwrap();
assert!(rx.try_recv().is_err());
helper.request_token();
rx.recv().unwrap().unwrap();
helper.request_token();
rx.recv().unwrap().unwrap();
helper.request_token();
helper.request_token();
rx.recv().unwrap().unwrap();
rx.recv().unwrap().unwrap();
helper.request_token();
helper.request_token();
drop(helper);
}
#[test]
fn prompt_shutdown() {
for _ in 0..100 {
let client = jobserver::Client::new(4).unwrap();
let count = Arc::new(AtomicU32::new(0));
let count2 = count.clone();
let tokens = Arc::new(Mutex::new(Vec::new()));
let helper = client
.into_helper_thread(move |token| {
tokens.lock().unwrap().push(token);
count2.fetch_add(1, Ordering::SeqCst);
})
.unwrap();
// Request more tokens than what are available.
for _ in 0..5 {
helper.request_token();
}
// Wait for at least some of the requests to finish.
while count.load(Ordering::SeqCst) < 3 {
std::thread::yield_now();
}
// Drop helper
let t = std::time::Instant::now();
drop(helper);
let d = t.elapsed();
assert!(d.as_secs_f64() < 0.5);
}
}

View File

@@ -0,0 +1,81 @@
use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::net::{TcpListener, TcpStream};
use std::process::Command;
use jobserver::Client;
macro_rules! t {
($e:expr) => {
match $e {
Ok(e) => e,
Err(e) => panic!("{} failed with {}", stringify!($e), e),
}
};
}
fn main() {
if env::var("_DO_THE_TEST").is_ok() {
std::process::exit(
Command::new(env::var_os("MAKE").unwrap())
.env("MAKEFLAGS", env::var_os("CARGO_MAKEFLAGS").unwrap())
.env_remove("_DO_THE_TEST")
.args(&env::args_os().skip(1).collect::<Vec<_>>())
.status()
.unwrap()
.code()
.unwrap_or(1),
);
}
if let Ok(s) = env::var("TEST_ADDR") {
let mut contents = Vec::new();
t!(t!(TcpStream::connect(&s)).read_to_end(&mut contents));
return;
}
let c = t!(Client::new(1));
let td = tempfile::tempdir().unwrap();
let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string());
let me = t!(env::current_exe());
let me = me.to_str().unwrap();
let mut cmd = Command::new(&me);
cmd.current_dir(td.path());
cmd.env("MAKE", prog);
cmd.env("_DO_THE_TEST", "1");
t!(t!(File::create(td.path().join("Makefile"))).write_all(
format!(
"\
all: foo bar
foo:
\t{0}
bar:
\t{0}
",
me
)
.as_bytes()
));
// We're leaking one extra token to `make` sort of violating the makefile
// jobserver protocol. It has the desired effect though.
c.configure(&mut cmd);
let listener = t!(TcpListener::bind("127.0.0.1:0"));
let addr = t!(listener.local_addr());
cmd.env("TEST_ADDR", addr.to_string());
let mut child = t!(cmd.spawn());
// We should get both connections as the two programs should be run
// concurrently.
let a = t!(listener.accept());
let b = t!(listener.accept());
drop((a, b));
assert!(t!(child.wait()).success());
}

181
vendor/jobserver/tests/server.rs vendored Normal file
View File

@@ -0,0 +1,181 @@
use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::process::Command;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
use std::sync::Arc;
use std::thread;
use jobserver::Client;
macro_rules! t {
($e:expr) => {
match $e {
Ok(e) => e,
Err(e) => panic!("{} failed with {}", stringify!($e), e),
}
};
}
#[test]
fn server_smoke() {
let c = t!(Client::new(1));
drop(c.acquire().unwrap());
drop(c.acquire().unwrap());
}
#[test]
fn server_multiple() {
let c = t!(Client::new(2));
let a = c.acquire().unwrap();
let b = c.acquire().unwrap();
drop((a, b));
}
#[test]
fn server_available() {
let c = t!(Client::new(10));
assert_eq!(c.available().unwrap(), 10);
let a = c.acquire().unwrap();
assert_eq!(c.available().unwrap(), 9);
drop(a);
assert_eq!(c.available().unwrap(), 10);
}
#[test]
fn server_none_available() {
let c = t!(Client::new(2));
assert_eq!(c.available().unwrap(), 2);
let a = c.acquire().unwrap();
assert_eq!(c.available().unwrap(), 1);
let b = c.acquire().unwrap();
assert_eq!(c.available().unwrap(), 0);
drop(a);
assert_eq!(c.available().unwrap(), 1);
drop(b);
assert_eq!(c.available().unwrap(), 2);
}
#[test]
fn server_blocks() {
let c = t!(Client::new(1));
let a = c.acquire().unwrap();
let hit = Arc::new(AtomicBool::new(false));
let hit2 = hit.clone();
let (tx, rx) = mpsc::channel();
let t = thread::spawn(move || {
tx.send(()).unwrap();
let _b = c.acquire().unwrap();
hit2.store(true, Ordering::SeqCst);
});
rx.recv().unwrap();
assert!(!hit.load(Ordering::SeqCst));
drop(a);
t.join().unwrap();
assert!(hit.load(Ordering::SeqCst));
}
#[test]
fn make_as_a_single_thread_client() {
let c = t!(Client::new(1));
let td = tempfile::tempdir().unwrap();
let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string());
let mut cmd = Command::new(prog);
cmd.current_dir(td.path());
t!(t!(File::create(td.path().join("Makefile"))).write_all(
b"
all: foo bar
foo:
\techo foo
bar:
\techo bar
"
));
// The jobserver protocol means that the `make` process itself "runs with a
// token", so we acquire our one token to drain the jobserver, and this
// should mean that `make` itself never has a second token available to it.
let _a = c.acquire();
c.configure(&mut cmd);
let output = t!(cmd.output());
println!(
"\n\t=== stderr\n\t\t{}",
String::from_utf8_lossy(&output.stderr).replace("\n", "\n\t\t")
);
println!(
"\t=== stdout\n\t\t{}",
String::from_utf8_lossy(&output.stdout).replace("\n", "\n\t\t")
);
assert!(output.status.success());
assert!(output.stderr.is_empty());
let stdout = String::from_utf8_lossy(&output.stdout).replace("\r\n", "\n");
let a = "\
echo foo
foo
echo bar
bar
";
let b = "\
echo bar
bar
echo foo
foo
";
assert!(stdout == a || stdout == b);
}
#[test]
fn make_as_a_multi_thread_client() {
let c = t!(Client::new(1));
let td = tempfile::tempdir().unwrap();
let prog = env::var("MAKE").unwrap_or_else(|_| "make".to_string());
let mut cmd = Command::new(prog);
cmd.current_dir(td.path());
t!(t!(File::create(td.path().join("Makefile"))).write_all(
b"
all: foo bar
foo:
\techo foo
bar:
\techo bar
"
));
// We're leaking one extra token to `make` sort of violating the makefile
// jobserver protocol. It has the desired effect though.
c.configure(&mut cmd);
let output = t!(cmd.output());
println!(
"\n\t=== stderr\n\t\t{}",
String::from_utf8_lossy(&output.stderr).replace("\n", "\n\t\t")
);
println!(
"\t=== stdout\n\t\t{}",
String::from_utf8_lossy(&output.stdout).replace("\n", "\n\t\t")
);
assert!(output.status.success());
}
#[test]
fn zero_client() {
let client = t!(Client::new(0));
let (tx, rx) = mpsc::channel();
let helper = client
.into_helper_thread(move |a| drop(tx.send(a)))
.unwrap();
helper.request_token();
helper.request_token();
for _ in 0..1000 {
assert!(rx.try_recv().is_err());
}
}