Showed the Rust compiler actually used on the information page (#1160)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-04 15:19:16 +01:00
1 parent 54c3f0b3e0
commit 677b650a79
9 files changed
+143 -16

No files matched your search

@@ -127,6 +127,7 @@
metadata.txt names the .NET SDK which the pipeline pins, but a local build may use another metadata.txt names the .NET SDK which the pipeline pins, but a local build may use another
one. Hence, we take the SDK which builds right now. Its .version file starts with the commit one. Hence, we take the SDK which builds right now. Its .version file starts with the commit
it was built from. The .NET runtime is not recorded at all: the app asks for it while it runs. it was built from. The .NET runtime is not recorded at all: the app asks for it while it runs.
Neither is the Rust compiler, which the app learns from the runtime it built.
--> -->
<PropertyGroup> <PropertyGroup>
<DotnetSdkVersionFile>$(NetCoreRoot)sdk/$(NETCoreSdkVersion)/.version</DotnetSdkVersionFile> <DotnetSdkVersionFile>$(NetCoreRoot)sdk/$(NETCoreSdkVersion)/.version</DotnetSdkVersionFile>
@@ -142,7 +143,6 @@
<MetaBuild>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 2 ])</MetaBuild> <MetaBuild>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 2 ])</MetaBuild>
<MetaDotnetSdkVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 3 ])</MetaDotnetSdkVersion> <MetaDotnetSdkVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 3 ])</MetaDotnetSdkVersion>
<MetaDotnetSdkVersion Condition="'$(DotnetSdkVersionLines)' != ''">$(NETCoreSdkVersion) (commit $([System.String]::Copy( $(DotnetSdkVersionLines) ).Split( ';' )[ 0 ].Substring( 0, 10 )))</MetaDotnetSdkVersion> <MetaDotnetSdkVersion Condition="'$(DotnetSdkVersionLines)' != ''">$(NETCoreSdkVersion) (commit $([System.String]::Copy( $(DotnetSdkVersionLines) ).Split( ';' )[ 0 ].Substring( 0, 10 )))</MetaDotnetSdkVersion>
<MetaRustVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 5 ])</MetaRustVersion>
<MetaMudBlazorVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 6 ])</MetaMudBlazorVersion> <MetaMudBlazorVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 6 ])</MetaMudBlazorVersion>
<MetaTauriVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 7 ])</MetaTauriVersion> <MetaTauriVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 7 ])</MetaTauriVersion>
<MetaAppCommitHash>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 8 ])</MetaAppCommitHash> <MetaAppCommitHash>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 8 ])</MetaAppCommitHash>
@@ -170,10 +170,9 @@
<_Parameter3>$(MetaBuild)</_Parameter3> <_Parameter3>$(MetaBuild)</_Parameter3>
<_Parameter3_TypeName>System.UInt32</_Parameter3_TypeName> <_Parameter3_TypeName>System.UInt32</_Parameter3_TypeName>
<_Parameter4>$(MetaDotnetSdkVersion)</_Parameter4> <_Parameter4>$(MetaDotnetSdkVersion)</_Parameter4>
<_Parameter5>$(MetaRustVersion)</_Parameter5> <_Parameter5>$(MetaMudBlazorVersion)</_Parameter5>
<_Parameter6>$(MetaMudBlazorVersion)</_Parameter6> <_Parameter6>$(MetaTauriVersion)</_Parameter6>
<_Parameter7>$(MetaTauriVersion)</_Parameter7> <_Parameter7>$(MetaAppCommitHash)</_Parameter7>
<_Parameter8>$(MetaAppCommitHash)</_Parameter8>
</AssemblyAttribute> </AssemblyAttribute>
<AssemblyAttribute Include="AIStudio.Tools.Metadata.MetaDataArchitecture"> <AssemblyAttribute Include="AIStudio.Tools.Metadata.MetaDataArchitecture">
<_Parameter1>$(MetaArchitecture)</_Parameter1> <_Parameter1>$(MetaArchitecture)</_Parameter1>
@@ -87,7 +87,9 @@ public partial class Information : MSGComponentBase
} }
}; };
private string VersionRust => $"{T("Used Rust compiler")}: v{META_DATA.RustVersion}"; private string VersionRust => string.IsNullOrWhiteSpace(this.RuntimeInfo.RustVersion)
? $"{T("Used Rust compiler")}: {T("unknown")}"
: $"{T("Used Rust compiler")}: v{this.RuntimeInfo.RustVersion}";
private string VersionDotnetRuntime => $"{T("Used .NET runtime")}: v{DotnetRuntimeVersion.Current}"; private string VersionDotnetRuntime => $"{T("Used .NET runtime")}: v{DotnetRuntimeVersion.Current}";
@@ -7,7 +7,6 @@ public class MetaDataAttribute(
string buildTime, string buildTime,
uint buildNum, uint buildNum,
string dotnetSdkVersion, string dotnetSdkVersion,
string rustVersion,
string mudBlazorVersion, string mudBlazorVersion,
string tauriVersion, string tauriVersion,
string appCommitHash string appCommitHash
@@ -20,12 +19,11 @@ public class MetaDataAttribute(
public uint BuildNum => buildNum; public uint BuildNum => buildNum;
/// <summary> /// <summary>
/// Gets the .NET SDK which built the app. The .NET runtime which runs it is <see cref="DotnetRuntimeVersion.Current"/>. /// Gets the .NET SDK which built the app. The .NET runtime which runs it is <see cref="DotnetRuntimeVersion.Current"/>,
/// and the Rust compiler which built the runtime is <see cref="Rust.RuntimeInfoResponse.RustVersion"/>.
/// </summary> /// </summary>
public string DotnetSdkVersion => dotnetSdkVersion; public string DotnetSdkVersion => dotnetSdkVersion;
public string RustVersion => rustVersion;
public string MudBlazorVersion => mudBlazorVersion; public string MudBlazorVersion => mudBlazorVersion;
public string TauriVersion => tauriVersion; public string TauriVersion => tauriVersion;
@@ -1,3 +1,11 @@
namespace AIStudio.Tools.Rust; namespace AIStudio.Tools.Rust;
public readonly record struct RuntimeInfoResponse(string WorkingDirectory, string ExecutablePath, LinuxPackageType LinuxPackageType, InstallationKind InstallationKind); /// <summary>
/// What the runtime tells about itself once the app starts.
/// </summary>
/// <param name="WorkingDirectory">The working directory of the runtime.</param>
/// <param name="ExecutablePath">The path of the runtime's executable.</param>
/// <param name="LinuxPackageType">How the Linux build was packaged.</param>
/// <param name="InstallationKind">Whether and how the app may update itself.</param>
/// <param name="RustVersion">The Rust compiler which built the runtime, e.g., "1.99.1 (commit 58cf8f9c3)". A runtime older than the app, as a development build may be, sends none.</param>
public readonly record struct RuntimeInfoResponse(string WorkingDirectory, string ExecutablePath, LinuxPackageType LinuxPackageType, InstallationKind InstallationKind, string? RustVersion);
@@ -11,6 +11,7 @@
- Improved how AI Studio colors code blocks: it now highlights them itself instead of through an extra script, which makes the app smaller. This works for Python, JavaScript, TypeScript, Java, C, C++, C#, Go, Kotlin, Rust, SQL, shell scripts, PowerShell, Lua, CSS, HTML, XML, JSON, and YAML; code in other languages, such as Ruby or PHP, appears without colors. When a code block names no language, AI Studio recognizes it from the code, colors the block, and suggests the matching file extension when you save it. - Improved how AI Studio colors code blocks: it now highlights them itself instead of through an extra script, which makes the app smaller. This works for Python, JavaScript, TypeScript, Java, C, C++, C#, Go, Kotlin, Rust, SQL, shell scripts, PowerShell, Lua, CSS, HTML, XML, JSON, and YAML; code in other languages, such as Ruby or PHP, appears without colors. When a code block names no language, AI Studio recognizes it from the code, colors the block, and suggests the matching file extension when you save it.
- Improved how carefully AI Studio reads the answers of AI models and external data sources: an answer that contains the same entry twice, possibly with two different values, is now refused instead of AI Studio picking one of them. This covers tool calls, the Assistant Builder, the Visual Briefing assistant, and ERI servers. - Improved how carefully AI Studio reads the answers of AI models and external data sources: an answer that contains the same entry twice, possibly with two different values, is now refused instead of AI Studio picking one of them. This covers tool calls, the Assistant Builder, the Visual Briefing assistant, and ERI servers.
- Improved the order of the models when you add or edit a provider: AI Studio now sorts the numbers in model names by their value, so that, for example, a model with version 4.10 comes before one with version 4.9, and the newest models stay at the top of the list. - Improved the order of the models when you add or edit a provider: AI Studio now sorts the numbers in model names by their value, so that, for example, a model with version 4.10 comes before one with version 4.9, and the newest models stay at the top of the list.
- Improved the information page, which now shows the Rust compiler that actually built your copy of AI Studio. Before, it always showed the compiler of our official builds, even for builds such as the Flatpak, which bring their own one.
- Changed the size of AI Studio on laptop screens: it now appears in the same size on every laptop, whatever the scaling of your operating system. If you prefer the previous size, choose "System scaling" for the interface size in the app settings. Contributed by Paul Koudelka. - Changed the size of AI Studio on laptop screens: it now appears in the same size on every laptop, whatever the scaling of your operating system. If you prefer the previous size, choose "System scaling" for the interface size in the app settings. Contributed by Paul Koudelka.
- Fixed AI Studio becoming unusable when an update failed after its download. The app now restarts with the version you have installed, so you can simply try the update again. - Fixed AI Studio becoming unusable when an update failed after its download. The app now restarts with the version you have installed, so you can simply try the update again.
- Fixed configuration plugins you installed yourself being able to change the tool settings of your organization, such as the minimum provider confidence of a tool. These settings now follow the same rules as all other settings, which also gives you back your own values once your organization removes its configuration. - Fixed configuration plugins you installed yourself being able to change the tool settings of your organization, such as the minimum provider confidence of a tool. These settings now follow the same rules as all other settings, which also gives you back your own values once your organization removes its configuration.
@@ -0,0 +1,40 @@
using System.Text.Json;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Services;
namespace AIStudio.Tests.Tools;
/// <summary>
/// Checks that the app reads what the runtime tells about itself, the Rust compiler which built it among it.
/// </summary>
/// <remarks>
/// RuntimeInfo in runtime/src/environment.rs names its fields in snake case, and the information page shows
/// the Rust compiler from it. A name which the app spelled differently would leave the page saying "unknown"
/// without failing anywhere. The app asks for this answer while it starts, so an answer without the compiler,
/// as a runtime older than the app sends it, must not fail either. The answers are read with the options of
/// RustService itself.
/// </remarks>
[TestFixture]
public sealed class RuntimeInfoResponseTests
{
[Test]
public void TheAppReadsTheRustCompilerWhichBuiltTheRuntime()
{
var response = JsonSerializer.Deserialize<RuntimeInfoResponse>("""{"working_directory":"/samples/work","executable_path":"/samples/app/mindwork-ai-studio","linux_package_type":"Flatpak","installation_kind":"User","rust_version":"1.98.1 (commit a1b2c3d4e)"}""", RustService.JSON_OPTIONS);
Assert.That(response, Is.EqualTo(new RuntimeInfoResponse("/samples/work", "/samples/app/mindwork-ai-studio", LinuxPackageType.FLATPAK, InstallationKind.USER, "1.98.1 (commit a1b2c3d4e)")));
}
[Test]
public void ARuntimeOlderThanTheAppNamesNoRustCompiler()
{
var response = JsonSerializer.Deserialize<RuntimeInfoResponse>("""{"working_directory":"/samples/work","executable_path":"/samples/app/mindwork-ai-studio","linux_package_type":"NotApplicable","installation_kind":"Development"}""", RustService.JSON_OPTIONS);
using (Assert.EnterMultipleScope())
{
Assert.That(response.InstallationKind, Is.EqualTo(InstallationKind.DEVELOPMENT));
Assert.That(response.RustVersion, Is.Null);
}
}
}
+49 -1
View File
@@ -51,16 +51,64 @@ fn main() {
println!("cargo:rustc-env=AI_STUDIO_ARCHITECTURE={rid}"); println!("cargo:rustc-env=AI_STUDIO_ARCHITECTURE={rid}");
let metadata = include_str!("../metadata.txt");
//
// The runtime reports the Rust compiler which built it. Line 6 of
// metadata.txt names only the compiler our pipeline is supposed to
// use; other builds, such as the Flatpak, bring their own one.
//
let rust_version = rust_compiler_version().unwrap_or_else(|issue| {
let configured_version = metadata.lines().nth(5).unwrap();
println!("cargo:warning=Cannot determine the Rust compiler which builds the runtime: {issue}. The runtime reports the one in metadata.txt instead: {configured_version}");
configured_version.to_string()
});
println!("cargo:rustc-env=AI_STUDIO_RUST_VERSION={rust_version}");
// //
// Read the current version and update the // Read the current version and update the
// Rust and Tauri configuration files: // Rust and Tauri configuration files:
// //
let metadata = include_str!("../metadata.txt");
let version = metadata.lines().next().unwrap(); let version = metadata.lines().next().unwrap();
update_cargo_toml("Cargo.toml", version); update_cargo_toml("Cargo.toml", version);
update_tauri_conf("tauri.conf.json", version); update_tauri_conf("tauri.conf.json", version);
} }
/// Asks the compiler which Cargo uses for the runtime about its version,
/// and returns it in the form of metadata.txt, e.g., "1.99.1 (commit 58cf8f9c3)".
///
/// Cargo hands every build script the compiler in `RUSTC`, which is the
/// one building the runtime, also when we cross-compile. The first line of
/// its answer reads, e.g., "rustc 1.99.1 (58cf8f9c3 2026-10-13)". The
/// version stays as the compiler names it, e.g., with "-nightly".
fn rust_compiler_version() -> Result<String, String> {
let rustc = std::env::var("RUSTC").map_err(|error| format!("Cargo did not name the compiler: {error}"))?;
let output = std::process::Command::new(&rustc)
.arg("-vV")
.output()
.map_err(|error| format!("'{rustc} -vV' did not run: {error}"))?;
if !output.status.success() {
return Err(format!("'{rustc} -vV' failed with {}", output.status));
}
let answer = String::from_utf8_lossy(&output.stdout);
let first_line = answer.lines().next().unwrap_or_default();
let unexpected = || format!("'{rustc} -vV' answered in an unexpected form: '{first_line}'");
let (version, rest) = first_line
.strip_prefix("rustc ")
.and_then(|rest| rest.split_once(" ("))
.ok_or_else(unexpected)?;
let commit = rest.split([' ', ')']).next().unwrap_or_default();
if version.is_empty() || commit.is_empty() || !commit.chars().all(|character| character.is_ascii_hexdigit()) {
return Err(unexpected());
}
Ok(format!("{version} (commit {commit})"))
}
/// Lets the linker embed our Windows application manifest into every program /// Lets the linker embed our Windows application manifest into every program
/// it links: the app as well as the test programs. Tauri's build helper adds /// it links: the app as well as the test programs. Tauri's build helper adds
/// its manifest only to the app. Tauri works around the same problem for its /// its manifest only to the app. Tauri works around the same problem for its
+24 -2
View File
@@ -1,3 +1,4 @@
use crate::metadata::RUST_VERSION;
use axum::http::StatusCode; use axum::http::StatusCode;
use axum::Json; use axum::Json;
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
@@ -126,6 +127,9 @@ pub struct RuntimeInfo {
pub executable_path: String, pub executable_path: String,
pub linux_package_type: LinuxPackageType, pub linux_package_type: LinuxPackageType,
pub installation_kind: InstallationKind, pub installation_kind: InstallationKind,
/// The Rust compiler which built the runtime, see `metadata::RUST_VERSION`.
pub rust_version: String,
} }
pub async fn get_runtime_info() -> Json<RuntimeInfo> { pub async fn get_runtime_info() -> Json<RuntimeInfo> {
@@ -138,6 +142,7 @@ pub async fn get_runtime_info() -> Json<RuntimeInfo> {
.unwrap_or_default(), .unwrap_or_default(),
linux_package_type: detect_linux_package_type(), linux_package_type: detect_linux_package_type(),
installation_kind: installation_kind(), installation_kind: installation_kind(),
rust_version: RUST_VERSION.to_string(),
}) })
} }
@@ -1419,7 +1424,7 @@ fn digits_of_hyphenated_guid(value: &str) -> Option<String> {
mod tests { mod tests {
use super::{ use super::{
canonical_enterprise_config_id, directory_is_writable, enterprise_env_key_suffix_ignoring_case, canonical_enterprise_config_id, directory_is_writable, enterprise_env_key_suffix_ignoring_case,
enterprise_environment_key_name, enterprise_policy_file_slot_suffix, enterprise_environment_key_name, enterprise_policy_file_slot_suffix, get_runtime_info,
has_managed_installation_marker, has_managed_installation_marker,
is_windows_machine_wide_installation, is_windows_per_user_installation, is_windows_machine_wide_installation, is_windows_per_user_installation,
load_external_http_custom_root_certificate_policy_from_directories, load_external_http_custom_root_certificate_policy_from_directories,
@@ -1430,7 +1435,7 @@ mod tests {
EnterpriseConfig, EnterpriseSourceData, EnterpriseSourceValue, EnterpriseSourceValues, EnterpriseConfig, EnterpriseSourceData, EnterpriseSourceValue, EnterpriseSourceValues,
ExternalHttpCustomRootCertificatePolicy, InstallationKind, LinuxPackageType, ExternalHttpCustomRootCertificatePolicy, InstallationKind, LinuxPackageType,
ENTERPRISE_ENV_CONFIG_ID_PREFIX, ENTERPRISE_ENV_CONFIG_SERVER_URL_PREFIX, ENTERPRISE_ENV_CONFIG_ID_PREFIX, ENTERPRISE_ENV_CONFIG_SERVER_URL_PREFIX,
MANAGED_INSTALLATION_MARKER_FILE_NAME, MANAGED_INSTALLATION_MARKER_FILE_NAME, RUST_VERSION,
}; };
use std::collections::HashMap; use std::collections::HashMap;
use std::fs; use std::fs;
@@ -1473,6 +1478,23 @@ mod tests {
} }
} }
/// The information page of the app shows the Rust compiler which built the runtime. The
/// app reads it as `rust_version`, in the form of metadata.txt, which the build script
/// keeps: the version as the compiler names it, and the short hash of its commit.
#[tokio::test]
async fn runtime_info_names_the_rust_compiler_which_built_the_runtime() {
let runtime_info = get_runtime_info().await.0;
assert_eq!(serde_json::to_value(&runtime_info).unwrap()["rust_version"], RUST_VERSION);
let (version, commit) = RUST_VERSION
.strip_suffix(')')
.and_then(|rest| rest.split_once(" (commit "))
.unwrap_or_else(|| panic!("'{RUST_VERSION}' does not read '<version> (commit <hash>)'"));
assert!(version.starts_with(|character: char| character.is_ascii_digit()), "'{version}' is no version of the compiler");
assert!(!commit.is_empty() && commit.chars().all(|character| character.is_ascii_hexdigit()), "'{commit}' is no commit hash");
}
fn enterprise_config( fn enterprise_config(
id: &str, id: &str,
server_url: &str, server_url: &str,
+11 -2
View File
@@ -3,6 +3,13 @@ use std::sync::Mutex;
pub static META_DATA: Lazy<Mutex<Option<MetaData>>> = Lazy::new(|| Mutex::new(None)); pub static META_DATA: Lazy<Mutex<Option<MetaData>>> = Lazy::new(|| Mutex::new(None));
/// The Rust compiler which built the runtime, e.g., "1.99.1 (commit 58cf8f9c3)".
///
/// The build script asks the compiler itself. Line 6 of metadata.txt names
/// only the one our pipeline is supposed to use, while other builds, such
/// as the Flatpak, bring their own compiler.
pub const RUST_VERSION: &str = env!("AI_STUDIO_RUST_VERSION");
#[derive(Clone)] #[derive(Clone)]
pub struct MetaData { pub struct MetaData {
pub app_version: String, pub app_version: String,
@@ -34,7 +41,9 @@ impl MetaData {
let build_number = metadata_lines.next().unwrap(); let build_number = metadata_lines.next().unwrap();
let dotnet_sdk_version = metadata_lines.next().unwrap(); let dotnet_sdk_version = metadata_lines.next().unwrap();
let dotnet_version = metadata_lines.next().unwrap(); let dotnet_version = metadata_lines.next().unwrap();
let rust_version = metadata_lines.next().unwrap();
// Skip the Rust compiler of the pipeline, see RUST_VERSION:
metadata_lines.next().unwrap();
let mud_blazor_version = metadata_lines.next().unwrap(); let mud_blazor_version = metadata_lines.next().unwrap();
let tauri_version = metadata_lines.next().unwrap(); let tauri_version = metadata_lines.next().unwrap();
let app_commit_hash = metadata_lines.next().unwrap(); let app_commit_hash = metadata_lines.next().unwrap();
@@ -51,7 +60,7 @@ impl MetaData {
dotnet_sdk_version: dotnet_sdk_version.to_string(), dotnet_sdk_version: dotnet_sdk_version.to_string(),
dotnet_version: dotnet_version.to_string(), dotnet_version: dotnet_version.to_string(),
mud_blazor_version: mud_blazor_version.to_string(), mud_blazor_version: mud_blazor_version.to_string(),
rust_version: rust_version.to_string(), rust_version: RUST_VERSION.to_string(),
tauri_version: tauri_version.to_string(), tauri_version: tauri_version.to_string(),
pdfium_version: pdfium_version.to_string(), pdfium_version: pdfium_version.to_string(),
vector_store_version: vector_store_version.to_string(), vector_store_version: vector_store_version.to_string(),