Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a23175a83 | ||
|
|
57492ab654 | ||
|
|
7054ff77a7 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
/data
|
/data
|
||||||
|
/dist
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
|
||||||
project(wtfnet LANGUAGES NONE)
|
|
||||||
|
|
||||||
set(CARGO_CMD cargo)
|
|
||||||
set(CARGO_TARGET_DIR "${CMAKE_BINARY_DIR}/cargo-target")
|
|
||||||
set(BIN_NAME "wtfn${CMAKE_EXECUTABLE_SUFFIX}")
|
|
||||||
set(BIN_PATH "${CARGO_TARGET_DIR}/release/${BIN_NAME}")
|
|
||||||
|
|
||||||
file(READ "${CMAKE_SOURCE_DIR}/crates/wtfnet-cli/Cargo.toml" CLI_TOML)
|
|
||||||
string(REGEX MATCH "version = \"([0-9]+\\.[0-9]+\\.[0-9]+)\"" CLI_VERSION_MATCH "${CLI_TOML}")
|
|
||||||
if(CMAKE_MATCH_1)
|
|
||||||
set(PACKAGE_VERSION "${CMAKE_MATCH_1}")
|
|
||||||
else()
|
|
||||||
set(PACKAGE_VERSION "0.1.0")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT "${BIN_PATH}"
|
|
||||||
COMMAND "${CMAKE_COMMAND}" -E env CARGO_TARGET_DIR="${CARGO_TARGET_DIR}"
|
|
||||||
"${CARGO_CMD}" build --release --workspace --bin wtfn
|
|
||||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
|
||||||
COMMENT "Building wtfn with cargo"
|
|
||||||
VERBATIM
|
|
||||||
)
|
|
||||||
|
|
||||||
add_custom_target(wtfnet_build ALL DEPENDS "${BIN_PATH}")
|
|
||||||
|
|
||||||
install(PROGRAMS "${BIN_PATH}" DESTINATION bin)
|
|
||||||
install(DIRECTORY "${CMAKE_SOURCE_DIR}/data" DESTINATION share/wtfnet)
|
|
||||||
|
|
||||||
add_dependencies(install wtfnet_build)
|
|
||||||
|
|
||||||
set(CPACK_PACKAGE_NAME "wtfnet")
|
|
||||||
set(CPACK_PACKAGE_VERSION "${PACKAGE_VERSION}")
|
|
||||||
set(CPACK_PACKAGE_FILE_NAME "wtfnet-${PACKAGE_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
|
|
||||||
if(WIN32)
|
|
||||||
set(CPACK_GENERATOR "ZIP")
|
|
||||||
else()
|
|
||||||
set(CPACK_GENERATOR "TGZ")
|
|
||||||
endif()
|
|
||||||
include(CPack)
|
|
||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -3235,7 +3235,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wtfnet-cli"
|
name = "wtfnet-cli"
|
||||||
version = "0.1.0"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
18
Makefile
18
Makefile
@@ -1,18 +0,0 @@
|
|||||||
BUILD_DIR ?= build
|
|
||||||
|
|
||||||
.PHONY: build configure package install clean
|
|
||||||
|
|
||||||
configure:
|
|
||||||
cmake -S . -B $(BUILD_DIR)
|
|
||||||
|
|
||||||
build: configure
|
|
||||||
cmake --build $(BUILD_DIR)
|
|
||||||
|
|
||||||
package: build
|
|
||||||
cmake --build $(BUILD_DIR) --target package
|
|
||||||
|
|
||||||
install: build
|
|
||||||
cmake --build $(BUILD_DIR) --target install
|
|
||||||
|
|
||||||
clean:
|
|
||||||
cmake -E rm -rf $(BUILD_DIR)
|
|
||||||
27
README.md
27
README.md
@@ -85,20 +85,33 @@ Lookup order:
|
|||||||
2) `data/` next to the CLI binary
|
2) `data/` next to the CLI binary
|
||||||
3) `data/` in the current working directory
|
3) `data/` in the current working directory
|
||||||
|
|
||||||
## Build and package
|
## Build
|
||||||
|
|
||||||
|
### Only build binary
|
||||||
```bash
|
```bash
|
||||||
cmake -S . -B build
|
cargo build --release
|
||||||
cmake --build build
|
|
||||||
cmake --build build --target package
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Install:
|
### Build and package
|
||||||
|
1. Prepare GeoLite2 databases (required `GeoLite2-ASN.mmdb` and `GeoLite2-Country.mmdb` ):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cmake --build build --target install
|
# Place your mmdb files under data/
|
||||||
|
mkdir data
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**: This step requires `python3` and `just`.
|
||||||
|
|
||||||
|
2. Use `just` to run build and package command (Note: you don't need bash environment on windows):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# You will find package under dist/, zip file on windows, tar.gz file on linux
|
||||||
|
just release
|
||||||
```
|
```
|
||||||
|
|
||||||
## HTTP/3 (experimental)
|
## HTTP/3 (experimental)
|
||||||
HTTP/3 support is feature-gated and incomplete. Do not enable it in production builds yet.
|
HTTP/3 support is feature-gated and best-effort. Enable it only when you want to test QUIC
|
||||||
|
connectivity.
|
||||||
|
|
||||||
To enable locally for testing:
|
To enable locally for testing:
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ use quinn::ClientConfig as QuinnClientConfig;
|
|||||||
#[cfg(feature = "http3")]
|
#[cfg(feature = "http3")]
|
||||||
use quinn::Endpoint;
|
use quinn::Endpoint;
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(feature = "http3")]
|
||||||
|
use quinn::crypto::rustls::QuicClientConfig;
|
||||||
|
#[cfg(feature = "http3")]
|
||||||
use webpki_roots::TLS_SERVER_ROOTS;
|
use webpki_roots::TLS_SERVER_ROOTS;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@@ -468,26 +470,54 @@ async fn http3_request(
|
|||||||
let port = parsed
|
let port = parsed
|
||||||
.port_or_known_default()
|
.port_or_known_default()
|
||||||
.ok_or_else(|| HttpError::Url("missing port".to_string()))?;
|
.ok_or_else(|| HttpError::Url("missing port".to_string()))?;
|
||||||
let ip = resolved_ips
|
|
||||||
.first()
|
|
||||||
.and_then(|value| value.parse::<IpAddr>().ok())
|
|
||||||
.ok_or_else(|| HttpError::Request("no resolved IPs for http3".to_string()))?;
|
|
||||||
|
|
||||||
let quinn_config = build_quinn_config()?;
|
let quinn_config = build_quinn_config()?;
|
||||||
|
let candidates = resolved_ips
|
||||||
|
.iter()
|
||||||
|
.filter_map(|value| value.parse::<IpAddr>().ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if candidates.is_empty() {
|
||||||
|
return Err(HttpError::Request("no resolved IPs for http3".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
let mut endpoint = Endpoint::client("0.0.0.0:0".parse().unwrap())
|
let mut endpoint_guard = None;
|
||||||
.map_err(|err| HttpError::Request(err.to_string()))?;
|
let mut connection = None;
|
||||||
endpoint.set_default_client_config(quinn_config);
|
let mut connect_ms = None;
|
||||||
|
for ip in candidates {
|
||||||
|
let bind_addr = match ip {
|
||||||
|
IpAddr::V4(_) => "0.0.0.0:0",
|
||||||
|
IpAddr::V6(_) => "[::]:0",
|
||||||
|
};
|
||||||
|
let mut endpoint = Endpoint::client(bind_addr.parse().unwrap())
|
||||||
|
.map_err(|err| HttpError::Request(err.to_string()))?;
|
||||||
|
endpoint.set_default_client_config(quinn_config.clone());
|
||||||
|
let connect_start = Instant::now();
|
||||||
|
let connecting = match endpoint.connect(SocketAddr::new(ip, port), host) {
|
||||||
|
Ok(connecting) => connecting,
|
||||||
|
Err(err) => {
|
||||||
|
warnings.push(format!("http3 connect failed to {ip}: {err}"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match timeout(Duration::from_millis(opts.timeout_ms), connecting).await {
|
||||||
|
Ok(Ok(conn)) => {
|
||||||
|
connect_ms = Some(connect_start.elapsed().as_millis());
|
||||||
|
connection = Some(conn);
|
||||||
|
endpoint_guard = Some(endpoint);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Ok(Err(err)) => {
|
||||||
|
warnings.push(format!("http3 connect failed to {ip}: {err}"));
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
warnings.push(format!("http3 connect to {ip} timed out"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let connect_start = Instant::now();
|
let connection = connection.ok_or_else(|| {
|
||||||
let connecting = endpoint
|
HttpError::Request("http3 connect failed for all resolved IPs".to_string())
|
||||||
.connect(SocketAddr::new(ip, port), host)
|
})?;
|
||||||
.map_err(|err| HttpError::Request(err.to_string()))?;
|
let connect_ms = connect_ms.unwrap_or_default();
|
||||||
let connection = timeout(Duration::from_millis(opts.timeout_ms), connecting)
|
|
||||||
.await
|
|
||||||
.map_err(|_| HttpError::Request("http3 connect timed out".to_string()))?
|
|
||||||
.map_err(|err| HttpError::Request(err.to_string()))?;
|
|
||||||
let connect_ms = connect_start.elapsed().as_millis();
|
|
||||||
|
|
||||||
let conn = h3_quinn::Connection::new(connection);
|
let conn = h3_quinn::Connection::new(connection);
|
||||||
let (mut driver, mut send_request) = h3::client::new(conn)
|
let (mut driver, mut send_request) = h3::client::new(conn)
|
||||||
@@ -563,30 +593,30 @@ async fn http3_request(
|
|||||||
|
|
||||||
warnings.push("http3 timing for tls/connect is best-effort".to_string());
|
warnings.push("http3 timing for tls/connect is best-effort".to_string());
|
||||||
|
|
||||||
Ok((
|
let _endpoint_guard = endpoint_guard;
|
||||||
HttpReport {
|
let report = HttpReport {
|
||||||
url: url.to_string(),
|
url: url.to_string(),
|
||||||
final_url: Some(final_url),
|
final_url: Some(final_url),
|
||||||
method: match opts.method {
|
method: match opts.method {
|
||||||
HttpMethod::Head => "HEAD".to_string(),
|
HttpMethod::Head => "HEAD".to_string(),
|
||||||
HttpMethod::Get => "GET".to_string(),
|
HttpMethod::Get => "GET".to_string(),
|
||||||
},
|
|
||||||
status: Some(status.as_u16()),
|
|
||||||
http_version: Some("HTTP/3".to_string()),
|
|
||||||
resolved_ips: resolved_ips.to_vec(),
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
warnings: Vec::new(),
|
|
||||||
timing: HttpTiming {
|
|
||||||
total_ms,
|
|
||||||
dns_ms: Some(dns_ms),
|
|
||||||
connect_ms: Some(connect_ms),
|
|
||||||
tls_ms: None,
|
|
||||||
ttfb_ms: Some(ttfb_ms),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
warnings,
|
status: Some(status.as_u16()),
|
||||||
))
|
http_version: Some("HTTP/3".to_string()),
|
||||||
|
resolved_ips: resolved_ips.to_vec(),
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
warnings: Vec::new(),
|
||||||
|
timing: HttpTiming {
|
||||||
|
total_ms,
|
||||||
|
dns_ms: Some(dns_ms),
|
||||||
|
connect_ms: Some(connect_ms),
|
||||||
|
tls_ms: None,
|
||||||
|
ttfb_ms: Some(ttfb_ms),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((report, warnings))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "http3")]
|
#[cfg(feature = "http3")]
|
||||||
@@ -594,10 +624,14 @@ fn build_quinn_config() -> Result<QuinnClientConfig, HttpError> {
|
|||||||
let mut roots = quinn::rustls::RootCertStore::empty();
|
let mut roots = quinn::rustls::RootCertStore::empty();
|
||||||
roots.extend(TLS_SERVER_ROOTS.iter().cloned());
|
roots.extend(TLS_SERVER_ROOTS.iter().cloned());
|
||||||
|
|
||||||
let mut client_config =
|
let mut crypto = quinn::rustls::ClientConfig::builder()
|
||||||
QuinnClientConfig::with_root_certificates(Arc::new(roots)).map_err(|err| {
|
.with_root_certificates(roots)
|
||||||
HttpError::Request(format!("quinn config error: {err}"))
|
.with_no_client_auth();
|
||||||
})?;
|
crypto.alpn_protocols = vec![b"h3".to_vec()];
|
||||||
|
let mut client_config = QuinnClientConfig::new(Arc::new(
|
||||||
|
QuicClientConfig::try_from(crypto)
|
||||||
|
.map_err(|err| HttpError::Request(format!("quinn config error: {err}")))?,
|
||||||
|
));
|
||||||
let mut transport = quinn::TransportConfig::default();
|
let mut transport = quinn::TransportConfig::default();
|
||||||
transport.keep_alive_interval(Some(Duration::from_secs(5)));
|
transport.keep_alive_interval(Some(Duration::from_secs(5)));
|
||||||
client_config.transport_config(Arc::new(transport));
|
client_config.transport_config(Arc::new(transport));
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ This document tracks current implementation status against the original design i
|
|||||||
- DNS watch uses `pnet` and is feature-gated as best-effort.
|
- DNS watch uses `pnet` and is feature-gated as best-effort.
|
||||||
|
|
||||||
## Gaps vs design (as of now)
|
## Gaps vs design (as of now)
|
||||||
- HTTP/3 is feature-gated and incomplete; not enabled in default builds.
|
- HTTP/3 is feature-gated and best-effort; not enabled in default builds.
|
||||||
- TLS verification is rustls-based (no OS-native verifier).
|
- TLS verification is rustls-based (no OS-native verifier).
|
||||||
- DNS leak DoH detection is heuristic and currently optional.
|
- DNS leak DoH detection is heuristic and currently optional.
|
||||||
|
|
||||||
|
|||||||
13
justfile
Normal file
13
justfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# justfile (cross-platform, no bash)
|
||||||
|
python := env_var_or_default("PYTHON", if os() == "windows" { "python" } else { "python3" })
|
||||||
|
dist_dir := "dist"
|
||||||
|
stage_root := "target/release-package"
|
||||||
|
|
||||||
|
default:
|
||||||
|
@just --list
|
||||||
|
|
||||||
|
release bin='' target='':
|
||||||
|
{{python}} scripts/release_meta.py --bin "{{bin}}" --target "{{target}}" --dist-dir "{{dist_dir}}" --stage-root "{{stage_root}}"
|
||||||
|
|
||||||
|
clean-dist:
|
||||||
|
{{python}} -c "import shutil; shutil.rmtree('dist', ignore_errors=True); shutil.rmtree('target/release-package', ignore_errors=True)"
|
||||||
175
scripts/release_meta.py
Normal file
175
scripts/release_meta.py
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tarfile
|
||||||
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd: list[str], *, capture: bool = False) -> str:
|
||||||
|
if capture:
|
||||||
|
return subprocess.check_output(cmd, text=True).strip()
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def cargo_metadata() -> dict[str, Any]:
|
||||||
|
out = run(["cargo", "metadata", "--no-deps", "--format-version", "1"], capture=True)
|
||||||
|
return json.loads(out)
|
||||||
|
|
||||||
|
|
||||||
|
def rustc_host_triple() -> str:
|
||||||
|
v = run(["rustc", "-vV"], capture=True)
|
||||||
|
for line in v.splitlines():
|
||||||
|
if line.startswith("host: "):
|
||||||
|
return line.split("host: ", 1)[1].strip()
|
||||||
|
raise RuntimeError("Could not determine host target triple from `rustc -vV`")
|
||||||
|
|
||||||
|
|
||||||
|
def is_windows_host() -> bool:
|
||||||
|
# Works for normal Windows Python and most MSYS/Cygwin Pythons too.
|
||||||
|
sp = sys.platform.lower()
|
||||||
|
ps = platform.system().lower()
|
||||||
|
return (
|
||||||
|
os.name == "nt"
|
||||||
|
or sp.startswith("win")
|
||||||
|
or sp.startswith("cygwin")
|
||||||
|
or sp.startswith("msys")
|
||||||
|
or "windows" in ps
|
||||||
|
or "cygwin" in ps
|
||||||
|
or "msys" in ps
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def exe_suffix_for_target(target_triple: str) -> str:
|
||||||
|
return ".exe" if "windows" in target_triple else ""
|
||||||
|
|
||||||
|
|
||||||
|
def find_bin_targets(meta: dict[str, Any]) -> list[tuple[str, str, str]]:
|
||||||
|
bins: list[tuple[str, str, str]] = []
|
||||||
|
for p in meta.get("packages", []):
|
||||||
|
for t in p.get("targets", []):
|
||||||
|
if "bin" in t.get("kind", []):
|
||||||
|
bins.append((p["name"], p["version"], t["name"]))
|
||||||
|
bins.sort(key=lambda x: (x[0], x[2], x[1])) # stable deterministic choice
|
||||||
|
return bins
|
||||||
|
|
||||||
|
|
||||||
|
def find_owner_package_for_bin(meta: dict[str, Any], bin_name: str) -> tuple[str, str]:
|
||||||
|
for p in meta.get("packages", []):
|
||||||
|
for t in p.get("targets", []):
|
||||||
|
if t.get("name") == bin_name and "bin" in t.get("kind", []):
|
||||||
|
return p["name"], p["version"]
|
||||||
|
raise RuntimeError(f"Could not find a package providing bin '{bin_name}'")
|
||||||
|
|
||||||
|
|
||||||
|
def stage_and_archive(
|
||||||
|
*,
|
||||||
|
pkg_name: str,
|
||||||
|
pkg_version: str,
|
||||||
|
bin_path: Path,
|
||||||
|
data_dir: Path,
|
||||||
|
dist_dir: Path,
|
||||||
|
stage_root: Path,
|
||||||
|
target_triple_for_name: str,
|
||||||
|
) -> Path:
|
||||||
|
pkg_base = f"{pkg_name}-v{pkg_version}-{target_triple_for_name}"
|
||||||
|
stage_dir = stage_root / pkg_base
|
||||||
|
stage_data_dir = stage_dir / "data"
|
||||||
|
|
||||||
|
if stage_root.exists():
|
||||||
|
shutil.rmtree(stage_root)
|
||||||
|
stage_data_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
dist_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
shutil.copy2(bin_path, stage_dir / bin_path.name)
|
||||||
|
|
||||||
|
mmdbs = sorted(data_dir.glob("*.mmdb")) if data_dir.exists() else []
|
||||||
|
if mmdbs:
|
||||||
|
for f in mmdbs:
|
||||||
|
shutil.copy2(f, stage_data_dir / f.name)
|
||||||
|
else:
|
||||||
|
print("WARN: no ./data/*.mmdb found; packaging binary only.", file=sys.stderr)
|
||||||
|
|
||||||
|
if is_windows_host():
|
||||||
|
out = dist_dir / f"{pkg_base}.zip"
|
||||||
|
with zipfile.ZipFile(out, "w", compression=zipfile.ZIP_DEFLATED) as z:
|
||||||
|
for p in stage_dir.rglob("*"):
|
||||||
|
if p.is_file():
|
||||||
|
z.write(p, arcname=str(Path(pkg_base) / p.relative_to(stage_dir)))
|
||||||
|
return out
|
||||||
|
else:
|
||||||
|
out = dist_dir / f"{pkg_base}.tar.gz"
|
||||||
|
with tarfile.open(out, "w:gz") as tf:
|
||||||
|
tf.add(stage_dir, arcname=pkg_base)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser(description="Build and package Rust binary + data/*.mmdb")
|
||||||
|
ap.add_argument("--bin", default="", help="Binary target name (optional)")
|
||||||
|
ap.add_argument("--target", default="", help="Cargo target triple (optional)")
|
||||||
|
ap.add_argument("--dist-dir", default="dist", help="Output directory for archives")
|
||||||
|
ap.add_argument("--stage-root", default="target/release-package", help="Staging directory root")
|
||||||
|
ap.add_argument("--data-dir", default="data", help="Directory containing .mmdb files")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
meta = cargo_metadata()
|
||||||
|
bins = find_bin_targets(meta)
|
||||||
|
if not bins:
|
||||||
|
print("ERROR: no binary targets found in workspace.", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
bin_name = args.bin.strip()
|
||||||
|
if not bin_name:
|
||||||
|
_, _, bin_name = bins[0]
|
||||||
|
print(f"INFO: --bin not provided; defaulting to '{bin_name}'", file=sys.stderr)
|
||||||
|
|
||||||
|
pkg_name, pkg_version = find_owner_package_for_bin(meta, bin_name)
|
||||||
|
|
||||||
|
host_triple = rustc_host_triple()
|
||||||
|
target_triple_for_name = args.target.strip() or host_triple
|
||||||
|
|
||||||
|
# Build only the owning package
|
||||||
|
build_cmd = ["cargo", "build", "-p", pkg_name, "--release"]
|
||||||
|
if args.target.strip():
|
||||||
|
build_cmd += ["--target", args.target.strip()]
|
||||||
|
run(build_cmd)
|
||||||
|
|
||||||
|
# Locate binary
|
||||||
|
exe_suffix = exe_suffix_for_target(target_triple_for_name)
|
||||||
|
bin_dir = Path("target") / (args.target.strip() if args.target.strip() else "release") / "release" \
|
||||||
|
if args.target.strip() else Path("target") / "release"
|
||||||
|
if args.target.strip():
|
||||||
|
bin_dir = Path("target") / args.target.strip() / "release"
|
||||||
|
|
||||||
|
bin_path = bin_dir / f"{bin_name}{exe_suffix}"
|
||||||
|
if not bin_path.exists():
|
||||||
|
print(f"ERROR: built binary not found: {bin_path}", file=sys.stderr)
|
||||||
|
print("Hint: pass the correct bin target name: just release bin=<name>", file=sys.stderr)
|
||||||
|
return 3
|
||||||
|
|
||||||
|
out = stage_and_archive(
|
||||||
|
pkg_name=pkg_name,
|
||||||
|
pkg_version=pkg_version,
|
||||||
|
bin_path=bin_path,
|
||||||
|
data_dir=Path(args.data_dir),
|
||||||
|
dist_dir=Path(args.dist_dir),
|
||||||
|
stage_root=Path(args.stage_root),
|
||||||
|
target_triple_for_name=target_triple_for_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Created: {out}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user