Stefan Hajnoczi <stefa...@redhat.com> writes:

> On Mon, Jun 10, 2024 at 09:22:36PM +0300, Manos Pitsidianakis wrote:
>> Add options for Rust in meson_options.txt, meson.build, configure to
>> prepare for adding Rust code in the followup commits.
>> 
>> `rust` is a reserved meson name, so we have to use an alternative.
>> `with_rust` was chosen.
>> 
>> Signed-off-by: Manos Pitsidianakis <manos.pitsidiana...@linaro.org>
>> ---
>> The cargo wrapper script hardcodes some rust target triples. This is 
>> just temporary.
>> ---
>>  .gitignore               |   2 +
>>  configure                |  12 +++
>>  meson.build              |  11 ++
>>  meson_options.txt        |   4 +
>>  scripts/cargo_wrapper.py | 211 +++++++++++++++++++++++++++++++++++++++
>>  5 files changed, 240 insertions(+)
>>  create mode 100644 scripts/cargo_wrapper.py
>> 
>> diff --git a/.gitignore b/.gitignore
>> index 61fa39967b..f42b0d937e 100644
>> --- a/.gitignore
>> +++ b/.gitignore
>> @@ -2,6 +2,8 @@
>>  /build/
>>  /.cache/
>>  /.vscode/
>> +/target/
>> +rust/**/target
>
> Are these necessary since the cargo build command-line below uses
> --target-dir <meson-build-dir>?
>
> Adding new build output directories outside build/ makes it harder to
> clean up the source tree and ensure no state from previous builds
> remains.

Indeed my tree looks like:

 $SRC
   /builds
     /buildA
     /buildB

etc. So I would expect the rust build stuff to be in the builddir
because I have multiple configurations.

>
>>  *.pyc
>>  .sdk
>>  .stgit-*
>> diff --git a/configure b/configure
>> index 38ee257701..c195630771 100755
>> --- a/configure
>> +++ b/configure
>> @@ -302,6 +302,9 @@ else
>>    objcc="${objcc-${cross_prefix}clang}"
>>  fi
>>  
>> +with_rust="auto"
>> +with_rust_target_triple=""
>> +
>>  ar="${AR-${cross_prefix}ar}"
>>  as="${AS-${cross_prefix}as}"
>>  ccas="${CCAS-$cc}"
>> @@ -760,6 +763,12 @@ for opt do
>>    ;;
>>    --gdb=*) gdb_bin="$optarg"
>>    ;;
>> +  --enable-rust) with_rust=enabled
>> +  ;;
>> +  --disable-rust) with_rust=disabled
>> +  ;;
>> +  --rust-target-triple=*) with_rust_target_triple="$optarg"
>> +  ;;
>>    # everything else has the same name in configure and meson
>>    --*) meson_option_parse "$opt" "$optarg"
>>    ;;
>> @@ -1796,6 +1805,9 @@ if test "$skip_meson" = no; then
>>    test -n "${LIB_FUZZING_ENGINE+xxx}" && meson_option_add 
>> "-Dfuzzing_engine=$LIB_FUZZING_ENGINE"
>>    test "$plugins" = yes && meson_option_add "-Dplugins=true"
>>    test "$tcg" != enabled && meson_option_add "-Dtcg=$tcg"
>> +  test "$with_rust" != enabled && meson_option_add "-Dwith_rust=$with_rust"
>> +  test "$with_rust" != enabled && meson_option_add "-Dwith_rust=$with_rust"
>
> Duplicate line.
>
>> +  test "$with_rust_target_triple" != "" && meson_option_add 
>> "-Dwith_rust_target_triple=$with_rust_target_triple"
>>    run_meson() {
>>      NINJA=$ninja $meson setup "$@" "$PWD" "$source_path"
>>    }
>> diff --git a/meson.build b/meson.build
>> index a9de71d450..3533889852 100644
>> --- a/meson.build
>> +++ b/meson.build
>> @@ -290,6 +290,12 @@ foreach lang : all_languages
>>    endif
>>  endforeach
>>  
>> +cargo = not_found
>> +if get_option('with_rust').allowed()
>> +  cargo = find_program('cargo', required: get_option('with_rust'))
>> +endif
>> +with_rust = cargo.found()
>> +
>>  # default flags for all hosts
>>  # We use -fwrapv to tell the compiler that we require a C dialect where
>>  # left shift of signed integers is well defined and has the expected
>> @@ -2066,6 +2072,7 @@ endif
>>  
>>  config_host_data = configuration_data()
>>  
>> +config_host_data.set('CONFIG_WITH_RUST', with_rust)
>>  audio_drivers_selected = []
>>  if have_system
>>    audio_drivers_available = {
>> @@ -4190,6 +4197,10 @@ if 'objc' in all_languages
>>  else
>>    summary_info += {'Objective-C compiler': false}
>>  endif
>> +summary_info += {'Rust support':      with_rust}
>> +if with_rust and get_option('with_rust_target_triple') != ''
>> +  summary_info += {'Rust target':     get_option('with_rust_target_triple')}
>> +endif
>>  option_cflags = (get_option('debug') ? ['-g'] : [])
>>  if get_option('optimization') != 'plain'
>>    option_cflags += ['-O' + get_option('optimization')]
>> diff --git a/meson_options.txt b/meson_options.txt
>> index 4c1583eb40..223491b731 100644
>> --- a/meson_options.txt
>> +++ b/meson_options.txt
>> @@ -366,3 +366,7 @@ option('qemu_ga_version', type: 'string', value: '',
>>  
>>  option('hexagon_idef_parser', type : 'boolean', value : true,
>>         description: 'use idef-parser to automatically generate TCG code for 
>> the Hexagon frontend')
>> +option('with_rust', type: 'feature', value: 'auto',
>> +       description: 'Enable Rust support')
>> +option('with_rust_target_triple', type : 'string', value: '',
>> +       description: 'Rust target triple')
>> diff --git a/scripts/cargo_wrapper.py b/scripts/cargo_wrapper.py
>> new file mode 100644
>> index 0000000000..d338effdaa
>> --- /dev/null
>> +++ b/scripts/cargo_wrapper.py
>> @@ -0,0 +1,211 @@
>> +#!/usr/bin/env python3
>> +# Copyright (c) 2020 Red Hat, Inc.
>> +# Copyright (c) 2023 Linaro Ltd.
>> +#
>> +# Authors:
>> +#  Manos Pitsidianakis <manos.pitsidiana...@linaro.org>
>> +#  Marc-André Lureau <marcandre.lur...@redhat.com>
>> +#
>> +# This work is licensed under the terms of the GNU GPL, version 2 or
>> +# later.  See the COPYING file in the top-level directory.
>> +
>> +import argparse
>> +import configparser
>> +import distutils.file_util
>> +import json
>> +import logging
>> +import os
>> +import os.path
>> +import re
>> +import subprocess
>> +import sys
>> +import pathlib
>> +import shutil
>> +import tomllib
>> +
>> +from pathlib import Path
>> +from typing import Any, Dict, List, Tuple
>> +
>> +RUST_TARGET_TRIPLES = (
>> +    "aarch64-unknown-linux-gnu",
>> +    "x86_64-unknown-linux-gnu",
>> +    "x86_64-apple-darwin",
>> +    "aarch64-apple-darwin",
>> +)
>
> Is this hardcoded to avoid calling `rustc --print target-list`?
>
> Or is this the support matrix? In that case it would be interesting to
> figure out the target triples for all host OSes and CPUs that QEMU is
> supported on.
>
>> +
>> +
>> +def cfg_name(name: str) -> str:
>> +    if (
>> +        name.startswith("CONFIG_")
>> +        or name.startswith("TARGET_")
>> +        or name.startswith("HAVE_")
>> +    ):
>> +        return name
>> +    return ""
>> +
>> +
>> +def generate_cfg_flags(header: str) -> List[str]:
>> +    with open(header, encoding="utf-8") as cfg:
>> +        config = [l.split()[1:] for l in cfg if l.startswith("#define")]
>> +
>> +    cfg_list = []
>> +    for cfg in config:
>> +        name = cfg_name(cfg[0])
>> +        if not name:
>> +            continue
>> +        if len(cfg) >= 2 and cfg[1] != "1":
>> +            continue
>> +        cfg_list.append("--cfg")
>> +        cfg_list.append(name)
>> +    return cfg_list
>> +
>> +
>> +def cargo_target_dir(args: argparse.Namespace) -> pathlib.Path:
>> +    return args.meson_build_dir
>> +
>> +
>> +def manifest_path(args: argparse.Namespace) -> pathlib.Path:
>> +    return args.crate_dir / "Cargo.toml"
>> +
>> +
>> +def get_cargo_rustc(args: argparse.Namespace) -> tuple[Dict[str, Any], 
>> List[str]]:
>> +    # See 
>> https://doc.rust-lang.org/cargo/reference/environment-variables.html
>> +    # Item `CARGO_ENCODED_RUSTFLAGS — A list of custom flags separated by
>> +    # 0x1f (ASCII Unit Separator) to pass to all compiler invocations that 
>> Cargo
>> +    # performs`
>> +    cfg = chr(0x1F).join(
>> +        [c for h in args.config_headers for c in generate_cfg_flags(h)]
>> +    )
>> +    target_dir = cargo_target_dir(args)
>> +    cargo_path = manifest_path(args)
>> +
>> +    cargo_cmd = [
>> +        "cargo",
>> +        "build",
>> +        "--target-dir",
>> +        str(target_dir),
>> +        "--manifest-path",
>> +        str(cargo_path),
>> +    ]
>> +    if args.target_triple:
>> +        cargo_cmd += ["--target", args.target_triple]
>> +    if args.profile == "release":
>> +        cargo_cmd += ["--release"]
>> +
>> +    env = os.environ
>> +    env["CARGO_ENCODED_RUSTFLAGS"] = cfg
>> +
>> +    return (env, cargo_cmd)
>> +
>> +
>> +def run_cargo(env: Dict[str, Any], cargo_cmd: List[str]) -> str:
>> +    envlog = " ".join(["{}={}".format(k, v) for k, v in env.items()])
>> +    cmdlog = " ".join(cargo_cmd)
>> +    logging.debug("Running %s %s", envlog, cmdlog)
>> +    try:
>> +        out = subprocess.check_output(
>> +            cargo_cmd,
>> +            env=dict(os.environ, **env),
>> +            stderr=subprocess.STDOUT,
>> +            universal_newlines=True,
>> +        )
>> +    except subprocess.CalledProcessError as err:
>> +        print("Environment: " + envlog)
>> +        print("Command: " + cmdlog)
>> +        print(err.output)
>> +        sys.exit(1)
>> +
>> +    return out
>> +
>> +
>> +def build_lib(args: argparse.Namespace) -> None:
>> +    logging.debug("build-lib")
>> +    target_dir = cargo_target_dir(args)
>> +    cargo_toml_path = manifest_path(args)
>> +
>> +    with open(cargo_toml_path, "rb") as f:
>> +        config = tomllib.load(f)
>> +
>> +    package_name = config["package"]["name"].strip('"').replace("-", "_")
>> +
>> +    liba_filename = "lib" + package_name + ".a"
>> +    liba = target_dir / args.target_triple / args.profile / liba_filename
>> +
>> +    env, cargo_cmd = get_cargo_rustc(args)
>> +    out = run_cargo(env, cargo_cmd)
>> +    logging.debug("cp %s %s", liba, args.outdir)
>> +    shutil.copy2(liba, args.outdir)
>> +
>> +
>> +def main() -> None:
>> +    parser = argparse.ArgumentParser()
>> +    parser.add_argument("-v", "--verbose", action="store_true")
>> +    parser.add_argument(
>> +        "--color",
>> +        metavar="WHEN",
>> +        choices=["auto", "always", "never"],
>> +        default="auto",
>> +        help="Coloring: auto, always, never",
>> +    )
>> +    parser.add_argument(
>> +        "--config-headers",
>> +        metavar="CONFIG_HEADER",
>> +        action="append",
>> +        dest="config_headers",
>> +        required=False,
>> +        default=[],
>> +    )
>> +    parser.add_argument(
>> +        "--meson-build-dir",
>> +        metavar="BUILD_DIR",
>> +        help="meson.current_build_dir()",
>> +        type=pathlib.Path,
>> +        dest="meson_build_dir",
>> +        required=True,
>> +    )
>> +    parser.add_argument(
>> +        "--meson-source-dir",
>> +        metavar="SOURCE_DIR",
>> +        help="meson.current_source_dir()",
>> +        type=pathlib.Path,
>> +        dest="meson_source_dir",
>> +        required=True,
>> +    )
>> +    parser.add_argument(
>> +        "--crate-dir",
>> +        metavar="CRATE_DIR",
>> +        type=pathlib.Path,
>> +        dest="crate_dir",
>> +        help="Absolute path that contains the manifest file of the crate to 
>> compile",
>> +        required=True,
>> +    )
>> +    parser.add_argument(
>> +        "--outdir",
>> +        metavar="OUTDIR",
>> +        type=pathlib.Path,
>> +        dest="outdir",
>> +        help="Path to copy compiled artifacts to for Meson to use.",
>> +        required=True,
>> +    )
>> +    parser.add_argument(
>> +        "--profile", type=str, choices=["release", "debug"], required=True
>> +    )
>> +    parser.add_argument(
>> +        "--target-triple", type=str, choices=RUST_TARGET_TRIPLES, 
>> required=True
>> +    )
>> +
>> +    subparsers = parser.add_subparsers()
>> +
>> +    buildlib = subparsers.add_parser("build-lib")
>> +    buildlib.set_defaults(func=build_lib)
>> +
>> +    args = parser.parse_args()
>> +    if args.verbose:
>> +        logging.basicConfig(level=logging.DEBUG)
>> +    logging.debug("args: %s", args)
>> +
>> +    args.func(args)
>> +
>> +
>> +if __name__ == "__main__":
>> +    main()
>> -- 
>> γαῖα πυρί μιχθήτω
>> 

-- 
Alex Bennée
Virtualisation Tech Lead @ Linaro

Reply via email to