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 254 255 256 257 258 259
|
#!/usr/bin/python3
"""
Wrapper around cargo to have it build using Debian settings.
Usage:
export PATH=/path/to/dir/of/this/script:$PATH
export CARGO_HOME=debian/cargo_home
cargo prepare-debian /path/to/local/registry [--link-from-system]
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. The "--link-from-system" flag
is optional; if you use it we will create /path/to/local/registry and symlink
the contents of /usr/share/cargo/registry into it. You are then responsible for
cleaning it up afterwards (a simple `rm -rf` should do).
See cargo:d/rules and dh-cargo:cargo.pm for more examples.
Make sure you add "Build-Depends: python3:native" if you use this directly.
If using this only indirectly via dh-cargo, then you only need "Build-Depends:
dh-cargo"; 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:
- 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-cargo, 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 in_cwd(p=None):
cwd = os.getcwd()
return os.path.join(cwd, p) if p else cwd
def prepare_debian(cargo_home, registry, cratespec, host_gnu_type, ldflags, link_from_system, extra_rustflags):
registry_path = in_cwd(registry)
if link_from_system:
log(f'linking {SYSTEM_REGISTRY}/* into {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)
elif not os.path.exists(registry_path):
raise ValueError(f'non-existent registry: {registry}')
rustflags = "-C debuginfo=2 -C strip=none --cap-lints warn".split()
rustflags.extend(["-C", f'linker={host_gnu_type}-gcc'])
for f in ldflags:
rustflags.extend(["-C", f'link-arg={f}'])
if link_from_system:
rustflags.extend([
# Note that this order is important! Rust evaluates these options in
# priority of reverse order, so if the second option were in front,
# it would never be used, because any paths in registry_path are
# also in in_cwd().
"--remap-path-prefix", f'{in_cwd()}={SYSTEM_REGISTRY}/{cratespec.replace("_", "-")}',
"--remap-path-prefix", f'{registry_path}={SYSTEM_REGISTRY}',
])
rustflags.extend(extra_rustflags.split())
# 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(os.path.join(cargo_home, 'config.toml'), "w") as fp:
fp.write("""[source.crates-io]
replace-with = "dh-cargo-registry"
[source.dh-cargo-registry]
directory = "{0}"
[build]
rustflags = {1}
[profile.release]
debug = true
""".format(registry_path, repr(rustflags)))
return 0
def install(destdir, cratespec, host_rust_type, crate_in_registry, install_prefix, *args):
crate, version = cratespec.rsplit("_", 1)
log(f"installing into destdir '{destdir}' prefix '{install_prefix}'")
install_target = destdir + install_prefix
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=" + in_cwd("target"),
"/usr/bin/cargo"] + list(args) +
([crate, "--vers", version] if crate_in_registry else ["--path", in_cwd()]) +
["--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 "target/%s/release/build/%s"-*/out 2>/dev/null | head -n1'''
% (host_rust_type, 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"):
os.execv("/usr/bin/cargo", ["cargo"] + list(args))
if any(f not in os.environ for f in FLAGS):
raise ValueError(f'not all of {FLAGS} set; did you call dpkg-buildflags?')
if any(f not in os.environ for f in ARCHES):
raise ValueError(f'not all of {ARCHES} set; did you include architecture.mk?')
build_options = os.getenv("DEB_BUILD_OPTIONS", "").split()
build_profiles = os.getenv("DEB_BUILD_PROFILES", "").split()
parallel = []
lto = ""
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 = "false"
elif opt_arg == "+lto":
lto = "\"thin\""
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
# 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(f'options = {build_options}, profiles = {build_profiles}, parallel = {parallel}, lto = {lto}')
log(f'rust_type = {host_rust_type}, gnu_type = {host_gnu_type}')
if "RUSTFLAGS" in os.environ:
# https://github.com/rust-lang/cargo/issues/6338
log('unsetting RUSTFLAGS for rust-lang/cargo#6338; add them to .cargo/config.toml')
extra_rustflags = os.environ["RUSTFLAGS"]
del os.environ["RUSTFLAGS"]
else:
extra_rustflags = ""
if args[0] == "prepare-debian":
registry = args[1]
link_from_system = False
if len(args) > 2 and args[2] == "--link-from-system":
link_from_system = True
return prepare_debian(cargo_home, registry,
os.environ["DEB_CARGO_CRATE"], host_gnu_type,
os.getenv("LDFLAGS", "").split(), link_from_system, extra_rustflags)
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, "--verbose", "--verbose"] +
parallel + ["--target", host_rust_type])
if lto:
newargs.append("--config")
newargs.append(f"profile.release.lto={lto}")
elif (subcmd is None) and (a == "clean"):
subcmd = a
newargs.extend([a, "--verbose", "--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 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 = os.path.join(cargo_home, 'config.toml')
if not os.path.exists(cargo_config):
raise ValueError(f'does not exist: {cargo_config}, did you run `cargo prepare-debian <registry>`?')
if subcmd == "install":
return install(os.getenv("DESTDIR", ""),
os.environ["DEB_CARGO_CRATE"],
host_rust_type,
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:]))
|