gst-plugins-rs/cargo_wrapper.py

205 lines
7.5 KiB
Python

#!/usr/bin/env python3
import hashlib
import re
import glob
import os
import shutil
import subprocess
import sys
import shlex
from argparse import ArgumentParser
from pathlib import Path as P
PARSER = ArgumentParser()
PARSER.add_argument('command', choices=['build', 'test'])
PARSER.add_argument('build_dir', type=P)
PARSER.add_argument('src_dir', type=P)
PARSER.add_argument('root_dir', type=P)
PARSER.add_argument('target', choices=['release', 'debug'])
PARSER.add_argument('prefix', type=P)
PARSER.add_argument('libdir', type=P)
PARSER.add_argument('--version', default=None)
PARSER.add_argument('--bin', default=None, type=P)
PARSER.add_argument('--features', nargs="+", default=[])
PARSER.add_argument('--packages', nargs="+", default=[])
PARSER.add_argument('--examples', nargs="+", default=[])
PARSER.add_argument('--lib-suffixes', nargs="+", default=[])
PARSER.add_argument('--exe-suffix')
PARSER.add_argument('--depfile')
PARSER.add_argument('--disable-doc', action="store_true", default=False)
def shlex_join(args):
if hasattr(shlex, 'join'):
return shlex.join(args)
return ' '.join([shlex.quote(arg) for arg in args])
def generate_depfile_for(fpath):
file_stem = fpath.parent / fpath.stem
depfile_content = ""
with open(f"{file_stem}.d", 'r') as depfile:
for l in depfile.readlines():
if l.startswith(str(file_stem)):
# We can't blindly split on `:` because on Windows that's part
# of the drive letter. Lucky for us, the format of the dep file
# is one of:
#
# /path/to/output: /path/to/src1 /path/to/src2
# /path/to/output:
#
# So we parse these two cases specifically
if l.endswith(':'):
output = l[:-1]
srcs = ''
else:
output, srcs = l.split(": ", maxsplit=2)
all_deps = []
for src in srcs.split(" "):
all_deps.append(src)
src = P(src)
if src.name == 'lib.rs':
# `rustc` doesn't take `Cargo.toml` into account
# but we need to
cargo_toml = src.parent.parent / 'Cargo.toml'
if cargo_toml.exists():
all_deps.append(str(cargo_toml))
depfile_content += f"{output}: {' '.join(all_deps)}\n"
return depfile_content
if __name__ == "__main__":
opts = PARSER.parse_args()
logdir = opts.root_dir / 'meson-logs'
logfile_path = logdir / f'{opts.src_dir.name}-cargo-wrapper.log'
logfile = open(logfile_path, mode='w', buffering=1)
print(opts, file=logfile)
cargo_target_dir = opts.build_dir / 'target'
env = os.environ.copy()
if 'PKG_CONFIG_PATH' in env:
pkg_config_path = env['PKG_CONFIG_PATH'].split(os.pathsep)
else:
pkg_config_path = []
pkg_config_path.append(str(opts.root_dir / 'meson-uninstalled'))
env['PKG_CONFIG_PATH'] = os.pathsep.join(pkg_config_path)
if 'NASM' in env:
env['PATH'] = os.pathsep.join([os.path.dirname(env['NASM']), env['PATH']])
rustc_target = None
if 'RUSTC' in env:
rustc_cmdline = shlex.split(env['RUSTC'])
# grab target from RUSTFLAGS
rust_flags = rustc_cmdline[1:] + shlex.split(env.get('RUSTFLAGS', ''))
if '--target' in rust_flags:
rustc_target_idx = rust_flags.index('--target')
_ = rust_flags.pop(rustc_target_idx) # drop '--target'
rustc_target = rust_flags.pop(rustc_target_idx)
env['RUSTFLAGS'] = shlex_join(rust_flags)
env['RUSTC'] = rustc_cmdline[0]
features = opts.features
if opts.command == 'build':
cargo_cmd = ['cargo']
if opts.bin or opts.examples:
cargo_cmd += ['build']
else:
cargo_cmd += ['cbuild']
if not opts.disable_doc:
features += ['doc']
if opts.target == 'release':
cargo_cmd.append('--release')
elif opts.command == 'test':
# cargo test
cargo_cmd = ['cargo', 'ctest', '--no-fail-fast', '--color=always']
else:
print("Unknown command:", opts.command, file=logfile)
sys.exit(1)
if rustc_target:
cargo_cmd += ['--target', rustc_target]
if features:
cargo_cmd += ['--features', ','.join(features)]
cargo_cmd += ['--target-dir', cargo_target_dir]
cargo_cmd += ['--manifest-path', opts.src_dir / 'Cargo.toml']
if opts.bin:
cargo_cmd.extend(['--bin', opts.bin.name])
else:
if not opts.examples:
cargo_cmd.extend(['--prefix', opts.prefix, '--libdir',
opts.prefix / opts.libdir])
for p in opts.packages:
cargo_cmd.extend(['-p', p])
for e in opts.examples:
cargo_cmd.extend(['--example', e])
def run(cargo_cmd, env):
print(cargo_cmd, env, file=logfile)
try:
subprocess.run(cargo_cmd, env=env, cwd=opts.src_dir, check=True)
except subprocess.SubprocessError:
sys.exit(1)
run(cargo_cmd, env)
if opts.command == 'build':
target_dir = cargo_target_dir / '**' / opts.target
if opts.bin:
exe = glob.glob(str(target_dir / opts.bin) + opts.exe_suffix, recursive=True)[0]
shutil.copy2(exe, opts.build_dir)
depfile_content = generate_depfile_for(P(exe))
else:
# Copy so files to build dir
depfile_content = ""
for suffix in opts.lib_suffixes:
for f in glob.glob(str(target_dir / f'*.{suffix}'), recursive=True):
libfile = P(f)
depfile_content += generate_depfile_for(libfile)
copied_file = (opts.build_dir / libfile.name)
try:
if copied_file.stat().st_mtime == libfile.stat().st_mtime:
print(f"{copied_file} has not changed.", file=logfile)
continue
except FileNotFoundError:
pass
print(f"Copying {copied_file}", file=logfile)
shutil.copy2(f, opts.build_dir)
# Copy examples to builddir
for example in opts.examples:
example_glob = str(target_dir / 'examples' / example) + opts.exe_suffix
exe = glob.glob(example_glob, recursive=True)[0]
shutil.copy2(exe, opts.build_dir)
depfile_content += generate_depfile_for(P(exe))
with open(opts.depfile, 'w') as depfile:
depfile.write(depfile_content)
# Copy generated pkg-config files
for f in glob.glob(str(target_dir / '*.pc'), recursive=True):
shutil.copy(f, opts.build_dir)
# Move -uninstalled.pc to meson-uninstalled
uninstalled = opts.build_dir / 'meson-uninstalled'
os.makedirs(uninstalled, exist_ok=True)
for f in opts.build_dir.glob('*-uninstalled.pc'):
# move() does not allow us to update the file so remove it if it already exists
dest = uninstalled / P(f).name
if dest.exists():
dest.unlink()
# move() takes paths from Python3.9 on
if ((sys.version_info.major >= 3) and (sys.version_info.minor >= 9)):
shutil.move(f, uninstalled)
else:
shutil.move(str(f), str(uninstalled))