diff mbox series

[RFC,v1,1/6] build-sys: Add rust feature option

Message ID 0933b669c8e47e1a78d21e56881e0933ef910461.1718040303.git.manos.pitsidianakis@linaro.org
State New
Headers show
Series Implement ARM PL011 in Rust | expand

Commit Message

Manos Pitsidianakis June 10, 2024, 6:22 p.m. UTC
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.pitsidianakis@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

Comments

Stefan Hajnoczi June 10, 2024, 7:25 p.m. UTC | #1
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.pitsidianakis@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.

>  *.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.pitsidianakis@linaro.org>
> +#  Marc-André Lureau <marcandre.lureau@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 June 11, 2024, 2:19 p.m. UTC | #2
Stefan Hajnoczi <stefanha@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.pitsidianakis@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.pitsidianakis@linaro.org>
>> +#  Marc-André Lureau <marcandre.lureau@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()
>> -- 
>> γαῖα πυρί μιχθήτω
>>
Manos Pitsidianakis June 11, 2024, 5:53 p.m. UTC | #3
On Tue, 11 Jun 2024 at 17:05, Stefan Hajnoczi <stefanha@redhat.com> wrote:
>
> 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.pitsidianakis@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.

Agreed! These build directories would show up when using cargo
directly instead of through the cargo_wrapper.py script, i.e. during
development. I'd consider it an edge case, it won't happen much and if
it does it's better to gitignore them than accidentally checking them
in. Also, whatever artifacts are in a `target` directory won't be used
for compilation with qemu inside a build directory.


> >  *.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.

Thanks!

>
> > +  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.pitsidianakis@linaro.org>
> > +#  Marc-André Lureau <marcandre.lureau@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.

Yes, it's what I tested it on (the x86-64-apple-darwin part through rosetta).

Do you think running -print target-list would be a better choice here?
This is only for providing the valid choices for the target triplet
CLI argument in argparse.


>
> > +
> > +
> > +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()
> > --
> > γαῖα πυρί μιχθήτω
> >
Stefan Hajnoczi June 11, 2024, 6:25 p.m. UTC | #4
On Tue, 11 Jun 2024 at 13:54, Manos Pitsidianakis
<manos.pitsidianakis@linaro.org> wrote:
>
> On Tue, 11 Jun 2024 at 17:05, Stefan Hajnoczi <stefanha@redhat.com> wrote:
> >
> > 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.pitsidianakis@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.
>
> Agreed! These build directories would show up when using cargo
> directly instead of through the cargo_wrapper.py script, i.e. during
> development. I'd consider it an edge case, it won't happen much and if
> it does it's better to gitignore them than accidentally checking them
> in. Also, whatever artifacts are in a `target` directory won't be used
> for compilation with qemu inside a build directory.

Why would someone bypass the build system? I don't think we should
encourage developers to do this.

>
>
> > >  *.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.
>
> Thanks!
>
> >
> > > +  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.pitsidianakis@linaro.org>
> > > +#  Marc-André Lureau <marcandre.lureau@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.
>
> Yes, it's what I tested it on (the x86-64-apple-darwin part through rosetta).
>
> Do you think running -print target-list would be a better choice here?
> This is only for providing the valid choices for the target triplet
> CLI argument in argparse.

How about not restricting choices? If the user specifies an invalid
choice then the compiler will fail with an error message. That seems
okay and avoids the issue altogether.

>
>
> >
> > > +
> > > +
> > > +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()
> > > --
> > > γαῖα πυρί μιχθήτω
> > >
>
Daniel P. Berrangé June 12, 2024, 8:04 a.m. UTC | #5
On Tue, Jun 11, 2024 at 02:25:39PM -0400, Stefan Hajnoczi wrote:
> On Tue, 11 Jun 2024 at 13:54, Manos Pitsidianakis
> <manos.pitsidianakis@linaro.org> wrote:
> >
> > On Tue, 11 Jun 2024 at 17:05, Stefan Hajnoczi <stefanha@redhat.com> wrote:
> > >
> > > 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.pitsidianakis@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/configure b/configure
> > > > index 38ee257701..c195630771 100755
> > > > --- a/configure
> > > > +++ b/configure

snip

> > > > +  test "$with_rust_target_triple" != "" && meson_option_add "-Dwith_rust_target_triple=$with_rust_target_triple"

So the --rust-target-triple is only needed when cross compiling,
but this is not the way we normally handle passing cross compiler
info to meson. Instead we create a meson cross compiler options
file containing the target info.

eg for ./configure --cross-prefix=x86_64-w64-mingw32-

we end up creating:

$ cat build/config-meson.cross 
# Automatically generated by configure - do not modify
[properties]
[built-in options]
c_args = []
cpp_args = []
objc_args = []
c_link_args = []
cpp_link_args = []
# environment defaults, can still be overridden on 
# the command line
werror = true
[project options]

[binaries]
c = ['x86_64-w64-mingw32-gcc','-m64']
cpp = ['x86_64-w64-mingw32-g++','-m64']
objc = ['x86_64-w64-mingw32-clang','-m64']
ar = ['x86_64-w64-mingw32-ar']
dlltool = ['x86_64-w64-mingw32-dlltool']
nm = ['x86_64-w64-mingw32-nm']
pkgconfig = ['x86_64-w64-mingw32-pkg-config']
pkg-config = ['x86_64-w64-mingw32-pkg-config']
ranlib = ['x86_64-w64-mingw32-ranlib']
strip = ['x86_64-w64-mingw32-strip']
widl = ['x86_64-w64-mingw32-widl']
windres = ['x86_64-w64-mingw32-windres']
windmc = ['x86_64-w64-mingw32-windmc']
[host_machine]
system = 'windows'
cpu_family = 'x86_64'
cpu = 'x86_64'
endian = 'little'


Should we not be passing the rust compiler target through
this meson options file by setting something like this

  rust = ['rustc', '--target', '$target_target_triple']


Also I don't think we should be requiring --rust-target-triple
to be passed by the user. For all the combinations we know &
test, we should have configure "do the right thing" and set a
suitable rust target triple based on the --cross-prefix argument
that is given, so there is no extra burden on users cross
compiling. Users should then only use --rust-target-triple
if our default logic is wrong for some reason.

> > > >    run_meson() {
> > > >      NINJA=$ninja $meson setup "$@" "$PWD" "$source_path"
> > > >    }
> > > > 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.pitsidianakis@linaro.org>
> > > > +#  Marc-André Lureau <marcandre.lureau@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.
> >
> > Yes, it's what I tested it on (the x86-64-apple-darwin part through rosetta).
> >
> > Do you think running -print target-list would be a better choice here?
> > This is only for providing the valid choices for the target triplet
> > CLI argument in argparse.
> 
> How about not restricting choices? If the user specifies an invalid
> choice then the compiler will fail with an error message. That seems
> okay and avoids the issue altogether.

Yes, we should not artifically limit the choices of target at all, as
we don't do that for existing cross compiler targets.

With regards,
Daniel
Marc-André Lureau June 12, 2024, 8:25 a.m. UTC | #6
Hi

On Wed, Jun 12, 2024 at 12:05 PM Daniel P. Berrangé <berrange@redhat.com>
wrote:

> On Tue, Jun 11, 2024 at 02:25:39PM -0400, Stefan Hajnoczi wrote:
> > On Tue, 11 Jun 2024 at 13:54, Manos Pitsidianakis
> > <manos.pitsidianakis@linaro.org> wrote:
> > >
> > > On Tue, 11 Jun 2024 at 17:05, Stefan Hajnoczi <stefanha@redhat.com>
> wrote:
> > > >
> > > > 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.pitsidianakis@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/configure b/configure
> > > > > index 38ee257701..c195630771 100755
> > > > > --- a/configure
> > > > > +++ b/configure
>
> snip
>
> > > > > +  test "$with_rust_target_triple" != "" && meson_option_add
> "-Dwith_rust_target_triple=$with_rust_target_triple"
>
> So the --rust-target-triple is only needed when cross compiling,
> but this is not the way we normally handle passing cross compiler
> info to meson. Instead we create a meson cross compiler options
> file containing the target info.
>
> eg for ./configure --cross-prefix=x86_64-w64-mingw32-
>
> we end up creating:
>
> $ cat build/config-meson.cross
> # Automatically generated by configure - do not modify
> [properties]
> [built-in options]
> c_args = []
> cpp_args = []
> objc_args = []
> c_link_args = []
> cpp_link_args = []
> # environment defaults, can still be overridden on
> # the command line
> werror = true
> [project options]
>
> [binaries]
> c = ['x86_64-w64-mingw32-gcc','-m64']
> cpp = ['x86_64-w64-mingw32-g++','-m64']
> objc = ['x86_64-w64-mingw32-clang','-m64']
> ar = ['x86_64-w64-mingw32-ar']
> dlltool = ['x86_64-w64-mingw32-dlltool']
> nm = ['x86_64-w64-mingw32-nm']
> pkgconfig = ['x86_64-w64-mingw32-pkg-config']
> pkg-config = ['x86_64-w64-mingw32-pkg-config']
> ranlib = ['x86_64-w64-mingw32-ranlib']
> strip = ['x86_64-w64-mingw32-strip']
> widl = ['x86_64-w64-mingw32-widl']
> windres = ['x86_64-w64-mingw32-windres']
> windmc = ['x86_64-w64-mingw32-windmc']
> [host_machine]
> system = 'windows'
> cpu_family = 'x86_64'
> cpu = 'x86_64'
> endian = 'little'
>
>
> Should we not be passing the rust compiler target through
> this meson options file by setting something like this
>
>   rust = ['rustc', '--target', '$target_target_triple']
>

Agree


>
>
> Also I don't think we should be requiring --rust-target-triple
> to be passed by the user. For all the combinations we know &
> test, we should have configure "do the right thing" and set a
> suitable rust target triple based on the --cross-prefix argument
> that is given, so there is no extra burden on users cross
> compiling. Users should then only use --rust-target-triple
> if our default logic is wrong for some reason.
>
>
Then I think we would need to maintain some mapping between GNU
target-triplets and Rust. It would be convenient to allow users to
set/overwrite it though.
diff mbox series

Patch

diff --git a/.gitignore b/.gitignore
index 61fa39967b..f42b0d937e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,8 @@ 
 /build/
 /.cache/
 /.vscode/
+/target/
+rust/**/target
 *.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"
+  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.pitsidianakis@linaro.org>
+#  Marc-André Lureau <marcandre.lureau@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",
+)
+
+
+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()