1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
|
#!/usr/bin/python3
"""
Wrapper around cargo to have it build using Debian settings.
SPDX-FileCopyrightText: 2022-2024 Jonas Smedegaard <dr@jones.dk>
SPDX-FileCopyrightText: 2015-2016 Luca Bruno <lucab@debian.org>
SPDX-FileCopyrightText: 2017-2019 Vasudeva Kamath <vasudev@copyninja.info>
SPDX-FileCopyrightText: 2016-2019 Ximin Luo <infinity0@debian.org>
SPDX-License-Identifier: (Apache-2.0 or MIT) and GPL-3-or-later
Usage:
export PATH=/path/to/dir/of/this/script:$PATH
export CARGO_HOME=debian/cargo_home
cargo prepare-debian /path/to/local/registry
cargo build
cargo test
cargo install
cargo clean
[rm -rf /path/to/local/registry]
The "prepare-debian" subcommand writes a config file to $CARGO_HOME
that makes the subsequent invocations use our Debian flags,
creates /path/to/local/registry and symlinks
the contents of /usr/share/cargo/registry into it. You are then responsible for
cleaning it up afterwards (a simple `rm -rf` should do).
Make sure you add "Build-Depends: python3:native" if you use this directly.
If using this only indirectly via dh-rust, then you only need "Build-Depends:
dh-rust"; this is a general principle when declaring dependencies.
If CARGO_HOME doesn't end with debian/cargo_home, then this script does nothing
and passes through directly to cargo.
Otherwise, you *must* set the following environment variables:
- (required only for `cargo install`) DEB_CARGO_CRATE
${crate}_${version} of whatever you're building.
- CFLAGS CXXFLAGS CPPFLAGS LDFLAGS [*]
- DEB_HOST_GNU_TYPE DEB_HOST_RUST_TYPE [*]
- (required only for `cargo install`) DESTDIR
DESTDIR to install build artifacts under. If running via dh-rust, this will
be set automatically by debhelper, see `dh_auto_install` for details.
- (optional) DEB_BUILD_OPTIONS DEB_BUILD_PROFILES
- (optional) DEB_CARGO_INSTALL_PREFIX
Prefix to install build artifacts under. Default: /usr. Sometimes you might
want to change this to /usr/lib/cargo if the binary clashes with something
else, and then symlink it into /usr/bin under an alternative name.
- (optional) DEB_CARGO_CRATE_IN_REGISTRY
Whether the crate is in the local-registry (1) or cwd (0, empty, default).
For the envvars marked [*], it is easiest to set these in your d/rules via:
include /usr/share/dpkg/architecture.mk
include /usr/share/dpkg/buildflags.mk
include /usr/share/rustc/architecture.mk
export CFLAGS CXXFLAGS CPPFLAGS LDFLAGS
export DEB_HOST_RUST_TYPE DEB_HOST_GNU_TYPE
"""
import os
import os.path
import shutil
import subprocess
import sys
FLAGS = "CFLAGS CXXFLAGS CPPFLAGS LDFLAGS"
ARCHES = "DEB_HOST_GNU_TYPE DEB_HOST_RUST_TYPE"
SYSTEM_REGISTRY = "/usr/share/cargo/registry"
def log(*args):
print("debian cargo wrapper:", *args, file=sys.stderr, flush=True)
def logrun(*args, **kwargs):
log("running subprocess", args, kwargs)
return subprocess.run(*args, **kwargs)
def sourcepath(p=None):
return os.path.join(os.getcwd(), p) if p else os.getcwd()
def prepare_debian(cargo_home, registry, host_gnu_type, ldflags):
registry_path = sourcepath(registry)
log("linking %s/* into %s/" % (SYSTEM_REGISTRY, registry_path))
os.makedirs(registry_path, exist_ok=True)
crates = os.listdir(SYSTEM_REGISTRY) if os.path.isdir(SYSTEM_REGISTRY) else []
for c in crates:
target = os.path.join(registry_path, c)
if not os.path.islink(target):
os.symlink(os.path.join(SYSTEM_REGISTRY, c), target)
rustflags = "-C debuginfo=2 --cap-lints warn".split()
rustflags.extend(["-C", "linker=%s-gcc" % host_gnu_type])
for f in ldflags:
rustflags.extend(["-C", "link-arg=%s" % f])
rustflags.extend(["--remap-path-prefix", "%s=%s" % (registry_path, SYSTEM_REGISTRY)])
# TODO: we cannot enable this until dh_shlibdeps works correctly; atm we get:
# dpkg-shlibdeps: warning: can't extract name and version from library name 'libstd-XXXXXXXX.so'
# and the resulting cargo.deb does not depend on the correct version of libstd-rust-1.XX
# We probably need to add override_dh_makeshlibs to d/rules of rustc
#rustflags.extend(["-C", "prefer-dynamic"])
os.makedirs(cargo_home, exist_ok=True)
with open("%s/config.toml" % cargo_home, "w") as fp:
fp.write("""[source.crates-io]
replace-with = "dh-cargo-registry"
[source.dh-cargo-registry]
directory = "{0}"
[target.'cfg(all())']
rustflags = {1}
""".format(registry_path, repr(rustflags)))
return 0
def install(destdir, cratespec, host_rust_type, profile, crate_in_registry, install_prefix, *args):
crate, version = cratespec.rsplit("_", 1)
builddir = os.getenv("DEB_BUILDDIR", "target")
log("installing into destdir '%s' prefix '%s'" % (destdir, install_prefix))
install_target = destdir + install_prefix
path_args = [] if "--path" in args else ["--path", sourcepath()]
logrun(["env", "RUST_BACKTRACE=1",
# set CARGO_TARGET_DIR so build products are saved in target/
# normally `cargo install` deletes them when it exits
"CARGO_TARGET_DIR=" + sourcepath(builddir),
"/usr/bin/cargo"] + list(args) +
([crate, "--vers", version] if crate_in_registry else path_args) +
["--root", install_target], check=True)
logrun(["rm", "-f", "%s/.crates.toml" % install_target])
logrun(["rm", "-f", "%s/.crates2.json" % install_target])
# if there was a custom build output, symlink it to debian/cargo_out_dir
# hopefully cargo will provide a better solution in future https://github.com/rust-lang/cargo/issues/5457
r = logrun('''ls -td "%s/%s/%s/build/%s"-*/out 2>/dev/null | head -n1'''
% (builddir, host_rust_type, profile, crate), shell=True, stdout=subprocess.PIPE).stdout
r = r.decode("utf-8").rstrip()
if r:
logrun(["ln", "-sfT", "../%s" % r, "debian/cargo_out_dir"], check=True)
return 0
def main(*args):
cargo_home = os.getenv("CARGO_HOME", "")
if not cargo_home.endswith("debian/cargo_home"):
log(f"WARNING: falling back to simply calling upstream cargo, because CARGO_HOME does not end with debian/cargo_home: ", cargo_home)
os.execv("/usr/bin/cargo", ["cargo"] + list(args))
if any(f not in os.environ for f in FLAGS.split()):
raise ValueError("not all of %s set; did you call dpkg-buildflags?" % FLAGS)
if any(f not in os.environ for f in ARCHES.split()):
raise ValueError("not all of %s set; did you include architecture.mk?" % ARCHES)
build_options = os.getenv("DEB_BUILD_OPTIONS", "").split()
build_profiles = os.getenv("DEB_BUILD_PROFILES", "").split()
parallel = []
lto = 0
for o in build_options:
if o.startswith("parallel="):
parallel = ["-j" + o[9:]]
elif o.startswith("optimize="):
opt_arg = o[9:]
for arg in opt_arg.split(","):
if opt_arg == "-lto":
lto = -1
elif opt_arg == "+lto":
lto = 1
else:
log(f"WARNING: unhandled optimization flag: {opt_arg}")
nodoc = "nodoc" in build_options or "nodoc" in build_profiles
nocheck = "nocheck" in build_options or "nocheck" in build_profiles
noopt = "noopt" in build_options
verbose = []
if not "terse" in build_options:
verbose = ["--verbose", "--verbose"]
if noopt:
profile = os.getenv("CARGO_PROFILE", "dev")
else:
profile = os.getenv("CARGO_PROFILE", "release")
# note this is actually the "build target" type, see rustc's README.Debian
# for full details of the messed-up terminology here
host_rust_type = os.getenv("DEB_HOST_RUST_TYPE", "")
host_gnu_type = os.getenv("DEB_HOST_GNU_TYPE", "")
log("options, profiles, parallel, lto:", build_options, build_profiles, parallel, lto)
log("rust_type, gnu_type:", ", ".join([host_rust_type, host_gnu_type]))
if args[0] == "prepare-debian":
registry = args[1]
return prepare_debian(cargo_home, registry,
host_gnu_type,
os.getenv("LDFLAGS", "").split())
newargs = []
subcmd = None
for a in args:
if (subcmd is None) and (a in ("build", "rustc", "doc", "test", "bench", "install")):
subcmd = a
newargs.extend(["-Zavoid-dev-deps", a] + ["--profile", profile] + verbose +
parallel + ["--target", host_rust_type])
elif (subcmd is None) and (a in ("check", "run")):
subcmd = a
newargs.extend([a] + ["--profile", profile] + verbose + ["--target", host_rust_type])
elif (subcmd is None) and (a == "clean"):
subcmd = a
newargs.extend([a] + verbose)
else:
newargs.append(a)
if subcmd is not None and "--verbose" in newargs and "--quiet" in newargs:
newargs.remove("--quiet")
if nodoc and subcmd == "doc":
return 0
if nocheck and subcmd in ("test", "bench"):
return 0
if lto == 1:
newargs.append("--config profile.release.lto = \"thin\"")
elif lto == -1:
newargs.append("--config profile.release.lto = false")
if subcmd == "clean":
logrun(["env", "RUST_BACKTRACE=1", "/usr/bin/cargo"] + list(newargs), check=True)
if os.path.exists(cargo_home):
shutil.rmtree(cargo_home)
return 0
cargo_config = "%s/config.toml" % cargo_home
if not os.path.exists(cargo_config):
raise ValueError("does not exist: %s, did you run `cargo prepare-debian <registry>`?" % cargo_config)
if subcmd == "install":
return install(os.getenv("DESTDIR", ""),
os.environ["DEB_CARGO_CRATE"],
host_rust_type, profile,
os.getenv("DEB_CARGO_CRATE_IN_REGISTRY", "") == "1",
os.getenv("DEB_CARGO_INSTALL_PREFIX", "/usr"),
*newargs)
else:
return logrun(["env", "RUST_BACKTRACE=1", "/usr/bin/cargo"] + list(newargs)).returncode
if __name__ == "__main__":
sys.exit(main(*sys.argv[1:]))
|