chore: add support for debian 11 downloads (#15596)

This commit is contained in:
Andrey Lushnikov 2022-07-13 04:09:24 -07:00 committed by GitHub
parent ee7d60fcee
commit 83e3dbb1bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 290 additions and 117 deletions

View File

@ -15,9 +15,8 @@
*/
import { execSync } from 'child_process';
import fs from 'fs';
import os from 'os';
import { parseOSReleaseText } from '../utils/ubuntuVersion';
import { getLinuxDistributionInfoSync } from '../utils/linuxUtils';
let cachedUserAgent: string | undefined;
@ -44,14 +43,11 @@ function determineUserAgent(): string {
osIdentifier = 'macOS';
osVersion = `${version[0]}.${version[1]}`;
} else if (process.platform === 'linux') {
try {
// List of /etc/os-release values for different distributions could be
// found here: https://gist.github.com/aslushnikov/8ceddb8288e4cf9db3039c02e0f4fb75
const osReleaseText = fs.readFileSync('/etc/os-release', 'utf8');
const fields = parseOSReleaseText(osReleaseText);
osIdentifier = fields.get('id') || 'unknown';
osVersion = fields.get('version_id') || 'unknown';
} catch (e) {
const distroInfo = getLinuxDistributionInfoSync();
if (distroInfo) {
osIdentifier = distroInfo.id || 'linux';
osVersion = distroInfo.version || 'unknown';
} else {
// Linux distribution without /etc/os-release.
// Default to linux/unknown.
osIdentifier = 'linux';

View File

@ -20,7 +20,7 @@ import path from 'path';
import * as util from 'util';
import * as fs from 'fs';
import { lockfile } from '../../utilsBundle';
import { getUbuntuVersion } from '../../utils/ubuntuVersion';
import { getLinuxDistributionInfo } from '../../utils/linuxUtils';
import { fetchData } from '../../common/netUtils';
import { getClientLanguage } from '../../common/userAgent';
import { getFromENV, getAsBooleanFromENV, calculateSha1, wrapInASCIIBox } from '../../utils';
@ -84,6 +84,7 @@ const DOWNLOAD_PATHS = {
'ubuntu18.04-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
'ubuntu20.04-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
'ubuntu22.04-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
'debian11': 'builds/chromium/%s/chromium-linux.zip',
'mac10.13': 'builds/chromium/%s/chromium-mac.zip',
'mac10.14': 'builds/chromium/%s/chromium-mac.zip',
'mac10.15': 'builds/chromium/%s/chromium-mac.zip',
@ -103,6 +104,7 @@ const DOWNLOAD_PATHS = {
'ubuntu18.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
'ubuntu20.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
'ubuntu22.04-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
'debian11': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip',
'mac10.13': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip',
'mac10.14': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip',
'mac10.15': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip',
@ -122,6 +124,7 @@ const DOWNLOAD_PATHS = {
'ubuntu18.04-arm64': 'builds/chromium/%s/chromium-with-symbols-linux-arm64.zip',
'ubuntu20.04-arm64': 'builds/chromium/%s/chromium-with-symbols-linux-arm64.zip',
'ubuntu22.04-arm64': 'builds/chromium/%s/chromium-with-symbols-linux-arm64.zip',
'debian11': 'builds/chromium/%s/chromium-with-symbols-linux.zip',
'mac10.13': 'builds/chromium/%s/chromium-with-symbols-mac.zip',
'mac10.14': 'builds/chromium/%s/chromium-with-symbols-mac.zip',
'mac10.15': 'builds/chromium/%s/chromium-with-symbols-mac.zip',
@ -141,6 +144,7 @@ const DOWNLOAD_PATHS = {
'ubuntu18.04-arm64': undefined,
'ubuntu20.04-arm64': 'builds/firefox/%s/firefox-ubuntu-20.04-arm64.zip',
'ubuntu22.04-arm64': 'builds/firefox/%s/firefox-ubuntu-22.04-arm64.zip',
'debian11': 'builds/firefox/%s/firefox-debian-11.zip',
'mac10.13': 'builds/firefox/%s/firefox-mac-11.zip',
'mac10.14': 'builds/firefox/%s/firefox-mac-11.zip',
'mac10.15': 'builds/firefox/%s/firefox-mac-11.zip',
@ -160,6 +164,7 @@ const DOWNLOAD_PATHS = {
'ubuntu18.04-arm64': undefined,
'ubuntu20.04-arm64': undefined,
'ubuntu22.04-arm64': 'builds/firefox-beta/%s/firefox-beta-ubuntu-22.04-arm64.zip',
'debian11': 'builds/firefox-beta/%s/firefox-beta-debian-11.zip',
'mac10.13': 'builds/firefox-beta/%s/firefox-beta-mac-11.zip',
'mac10.14': 'builds/firefox-beta/%s/firefox-beta-mac-11.zip',
'mac10.15': 'builds/firefox-beta/%s/firefox-beta-mac-11.zip',
@ -179,6 +184,7 @@ const DOWNLOAD_PATHS = {
'ubuntu18.04-arm64': undefined,
'ubuntu20.04-arm64': 'builds/webkit/%s/webkit-ubuntu-20.04-arm64.zip',
'ubuntu22.04-arm64': 'builds/webkit/%s/webkit-ubuntu-22.04-arm64.zip',
'debian11': 'builds/webkit/%s/webkit-linux-universal.zip',
'mac10.13': undefined,
'mac10.14': 'builds/deprecated-webkit-mac-10.14/%s/deprecated-webkit-mac-10.14.zip',
'mac10.15': 'builds/webkit/%s/webkit-mac-10.15.zip',
@ -198,6 +204,7 @@ const DOWNLOAD_PATHS = {
'ubuntu18.04-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
'ubuntu20.04-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
'ubuntu22.04-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
'debian11': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
'mac10.13': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac10.14': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac10.15': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
@ -321,11 +328,11 @@ export class Registry {
const descriptors = readDescriptors(browsersJSON);
const findExecutablePath = (dir: string, name: keyof typeof EXECUTABLE_PATHS) => {
let tokens = undefined;
if (hostPlatform.startsWith('ubuntu') || hostPlatform.startsWith('generic-linux'))
if (process.platform === 'linux')
tokens = EXECUTABLE_PATHS[name]['linux'];
else if (hostPlatform.startsWith('mac'))
else if (process.platform === 'darwin')
tokens = EXECUTABLE_PATHS[name]['mac'];
else if (hostPlatform.startsWith('win'))
else if (process.platform === 'win32')
tokens = EXECUTABLE_PATHS[name]['win'];
return tokens ? path.join(dir, ...tokens) : undefined;
};
@ -607,10 +614,15 @@ export class Registry {
process.stdout.write('Skipping host requirements validation logic because `PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS` env variable is set.\n');
return;
}
const ubuntuVersion = await getUbuntuVersion();
if (browserName === 'firefox' && ubuntuVersion === '16.04')
const distributionInfo = await getLinuxDistributionInfo();
if (browserName === 'firefox' && distributionInfo?.id === 'ubuntu' && distributionInfo?.version === '16.04')
throw new Error(`Cannot launch Firefox on Ubuntu 16.04! Minimum required Ubuntu version for Firefox browser is 18.04`);
// Skip dependency validation for WebKit on non-ubuntu distributions since it takes
// forever and is not needed due to universal build.
if (os.platform() === 'linux' && browserName === 'webkit' && distributionInfo?.id !== 'ubuntu')
return;
if (os.platform() === 'linux')
return await validateDependenciesLinux(sdkLanguage, linuxLddDirectories.map(d => path.join(browserDirectory, d)), dlOpenLibraries);
if (os.platform() === 'win32' && os.arch() === 'x64')
@ -714,7 +726,7 @@ export class Registry {
if (!downloadPathTemplate || !executablePath)
throw new Error(`ERROR: Playwright does not support ${descriptor.name} on ${hostPlatform}`);
if (hostPlatform === 'generic-linux' || hostPlatform === 'generic-linux-arm64')
logPolitely('BEWARE: your OS is not officially supported by Playwright; downloading Ubuntu build as a fallback.');
logPolitely('BEWARE: your OS is not officially supported by Playwright; downloading fallback build.');
const downloadPath = util.format(downloadPathTemplate, descriptor.revision);
let downloadURLs = PLAYWRIGHT_CDN_MIRRORS.map(mirror => `${mirror}/${downloadPath}`) ;

View File

@ -647,6 +647,176 @@ export const deps: any = {
'libx264.so': 'libx264-163',
'libvpx.so.7': 'libvpx7'
},
},
'debian11': {
tools: [
'xvfb',
'fonts-noto-color-emoji',
'fonts-unifont',
'libfontconfig1',
'libfreetype6',
'xfonts-cyrillic',
'xfonts-scalable',
'fonts-liberation',
'fonts-ipafont-gothic',
'fonts-wqy-zenhei',
'fonts-tlwg-loma-otf',
'fonts-freefont-ttf',
],
chromium: [
'libasound2',
'libatk-bridge2.0-0',
'libatk1.0-0',
'libatspi2.0-0',
'libcairo2',
'libcups2',
'libdbus-1-3',
'libdrm2',
'libgbm1',
'libglib2.0-0',
'libnspr4',
'libnss3',
'libpango-1.0-0',
'libwayland-client0',
'libx11-6',
'libxcb1',
'libxcomposite1',
'libxdamage1',
'libxext6',
'libxfixes3',
'libxkbcommon0',
'libxrandr2'
],
firefox: [
'libasound2',
'libatk1.0-0',
'libcairo-gobject2',
'libcairo2',
'libdbus-1-3',
'libdbus-glib-1-2',
'libfontconfig1',
'libfreetype6',
'libgdk-pixbuf-2.0-0',
'libglib2.0-0',
'libgtk-3-0',
'libharfbuzz0b',
'libpango-1.0-0',
'libpangocairo-1.0-0',
'libx11-6',
'libx11-xcb1',
'libxcb-shm0',
'libxcb1',
'libxcomposite1',
'libxcursor1',
'libxdamage1',
'libxext6',
'libxfixes3',
'libxi6',
'libxrandr2',
'libxrender1',
'libxtst6'
],
webkit: [
// We use universal build on debian so webkit does not require any dependencies.
],
lib2package: {
'libasound.so.2': 'libasound2',
'libatk-1.0.so.0': 'libatk1.0-0',
'libatk-bridge-2.0.so.0': 'libatk-bridge2.0-0',
'libatomic.so.1': 'libatomic1',
'libatspi.so.0': 'libatspi2.0-0',
'libc.so.6': 'libc6',
'libcairo-gobject.so.2': 'libcairo-gobject2',
'libcairo.so.2': 'libcairo2',
'libcups.so.2': 'libcups2',
'libdbus-1.so.3': 'libdbus-1-3',
'libdbus-glib-1.so.2': 'libdbus-glib-1-2',
'libdl.so.2': 'libc6',
'libdrm.so.2': 'libdrm2',
'libEGL.so.1': 'libegl1',
'libenchant-2.so.2': 'libenchant-2-2',
'libepoxy.so.0': 'libepoxy0',
'libfontconfig.so.1': 'libfontconfig1',
'libfreetype.so.6': 'libfreetype6',
'libgbm.so.1': 'libgbm1',
'libgcc_s.so.1': 'libgcc-s1',
'libgcrypt.so.20': 'libgcrypt20',
'libgdk_pixbuf-2.0.so.0': 'libgdk-pixbuf-2.0-0',
'libgdk-3.so.0': 'libgtk-3-0',
'libgio-2.0.so.0': 'libglib2.0-0',
'libGLESv2.so.2': 'libgles2',
'libglib-2.0.so.0': 'libglib2.0-0',
'libGLX.so.0': 'libglx0',
'libgmodule-2.0.so.0': 'libglib2.0-0',
'libgobject-2.0.so.0': 'libglib2.0-0',
'libgpg-error.so.0': 'libgpg-error0',
'libgstallocators-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
'libgstapp-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
'libgstaudio-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
'libgstbase-1.0.so.0': 'libgstreamer1.0-0',
'libgstcodecparsers-1.0.so.0': 'libgstreamer-plugins-bad1.0-0',
'libgstfft-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
'libgstgl-1.0.so.0': 'libgstreamer-gl1.0-0',
'libgstpbutils-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
'libgstreamer-1.0.so.0': 'libgstreamer1.0-0',
'libgsttag-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
'libgstvideo-1.0.so.0': 'libgstreamer-plugins-base1.0-0',
'libgtk-3.so.0': 'libgtk-3-0',
'libharfbuzz-icu.so.0': 'libharfbuzz-icu0',
'libharfbuzz.so.0': 'libharfbuzz0b',
'libhyphen.so.0': 'libhyphen0',
'libjavascriptcoregtk-4.0.so.18': 'libjavascriptcoregtk-4.0-18',
'libjpeg.so.62': 'libjpeg62-turbo',
'liblcms2.so.2': 'liblcms2-2',
'libm.so.6': 'libc6',
'libmanette-0.2.so.0': 'libmanette-0.2-0',
'libnotify.so.4': 'libnotify4',
'libnspr4.so': 'libnspr4',
'libnss3.so': 'libnss3',
'libnssutil3.so': 'libnss3',
'libOpenGL.so.0': 'libopengl0',
'libopenjp2.so.7': 'libopenjp2-7',
'libopus.so.0': 'libopus0',
'libpango-1.0.so.0': 'libpango-1.0-0',
'libpangocairo-1.0.so.0': 'libpangocairo-1.0-0',
'libpng16.so.16': 'libpng16-16',
'libpthread.so.0': 'libc6',
'libsecret-1.so.0': 'libsecret-1-0',
'libsmime3.so': 'libnss3',
'libsoup-2.4.so.1': 'libsoup2.4-1',
'libsqlite3.so.0': 'libsqlite3-0',
'libstdc++.so.6': 'libstdc++6',
'libsystemd.so.0': 'libsystemd0',
'libtasn1.so.6': 'libtasn1-6',
'libvpx.so.6': 'libvpx6',
'libwayland-client.so.0': 'libwayland-client0',
'libwayland-egl.so.1': 'libwayland-egl1',
'libwayland-server.so.0': 'libwayland-server0',
'libwebkit2gtk-4.0.so.37': 'libwebkit2gtk-4.0-37',
'libwebpdemux.so.2': 'libwebpdemux2',
'libwoff2dec.so.1.0.2': 'libwoff1',
'libwpe-1.0.so.1': 'libwpe-1.0-1',
'libWPEBackend-fdo-1.0.so.1': 'libwpebackend-fdo-1.0-1',
'libWPEWebKit-1.0.so.3': 'libwpewebkit-1.0-3',
'libX11-xcb.so.1': 'libx11-xcb1',
'libX11.so.6': 'libx11-6',
'libxcb-shm.so.0': 'libxcb-shm0',
'libxcb.so.1': 'libxcb1',
'libXcomposite.so.1': 'libxcomposite1',
'libXcursor.so.1': 'libxcursor1',
'libXdamage.so.1': 'libxdamage1',
'libXext.so.6': 'libxext6',
'libXfixes.so.3': 'libxfixes3',
'libXi.so.6': 'libxi6',
'libxkbcommon.so.0': 'libxkbcommon0',
'libxml2.so.2': 'libxml2',
'libXrandr.so.2': 'libxrandr2',
'libXrender.so.1': 'libxrender1',
'libxslt.so.1': 'libxslt1.1',
'libXt.so.6': 'libxt6',
'libXtst.so.6': 'libxtst6',
'libz.so.1': 'libzadc4',
},
}
};

View File

@ -15,7 +15,7 @@
*/
import os from 'os';
import { getUbuntuVersionSync } from './ubuntuVersion';
import { getLinuxDistributionInfoSync } from './linuxUtils';
export type HostPlatform = 'win64' |
'mac10.13' |
@ -25,6 +25,7 @@ export type HostPlatform = 'win64' |
'mac12' | 'mac12-arm64' |
'ubuntu18.04' | 'ubuntu18.04-arm64' |
'ubuntu20.04' | 'ubuntu20.04-arm64' |
'debian11' |
'generic-linux' | 'generic-linux-arm64' |
'<unknown>';
@ -53,14 +54,19 @@ export const hostPlatform = ((): HostPlatform => {
}
if (platform === 'linux') {
const archSuffix = os.arch() === 'arm64' ? '-arm64' : '';
const ubuntuVersion = getUbuntuVersionSync();
if (!ubuntuVersion)
return ('generic-linux' + archSuffix) as HostPlatform;
if (parseInt(ubuntuVersion, 10) <= 19)
return ('ubuntu18.04' + archSuffix) as HostPlatform;
if (parseInt(ubuntuVersion, 10) <= 21)
return ('ubuntu20.04' + archSuffix) as HostPlatform;
return ('ubuntu22.04' + archSuffix) as HostPlatform;
const distroInfo = getLinuxDistributionInfoSync();
// Pop!_OS is ubuntu-based and has the same versions.
if (distroInfo?.id === 'ubuntu' || distroInfo?.id === 'pop') {
if (parseInt(distroInfo.version, 10) <= 19)
return ('ubuntu18.04' + archSuffix) as HostPlatform;
if (parseInt(distroInfo.version, 10) <= 21)
return ('ubuntu20.04' + archSuffix) as HostPlatform;
return ('ubuntu22.04' + archSuffix) as HostPlatform;
}
if (distroInfo?.id === 'debian' && distroInfo?.version === '11' && !archSuffix)
return 'debian11';
return ('generic-linux' + archSuffix) as HostPlatform;
}
if (platform === 'win32')
return 'win64';

View File

@ -0,0 +1,80 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fs from 'fs';
let didFailToReadOSRelease = false;
let osRelease: {
id: string,
version: string,
} | undefined;
export async function getLinuxDistributionInfo(): Promise<{ id: string, version: string } | undefined> {
if (process.platform !== 'linux')
return undefined;
if (!osRelease && !didFailToReadOSRelease) {
try {
// List of /etc/os-release values for different distributions could be
// found here: https://gist.github.com/aslushnikov/8ceddb8288e4cf9db3039c02e0f4fb75
const osReleaseText = await fs.promises.readFile('/etc/os-release', 'utf8');
const fields = parseOSReleaseText(osReleaseText);
osRelease = {
id: fields.get('id') ?? '',
version: fields.get('version_id') ?? '',
};
} catch (e) {
didFailToReadOSRelease = true;
}
}
return osRelease;
}
export function getLinuxDistributionInfoSync(): { id: string, version: string } | undefined {
if (process.platform !== 'linux')
return undefined;
if (!osRelease && !didFailToReadOSRelease) {
try {
// List of /etc/os-release values for different distributions could be
// found here: https://gist.github.com/aslushnikov/8ceddb8288e4cf9db3039c02e0f4fb75
const osReleaseText = fs.readFileSync('/etc/os-release', 'utf8');
const fields = parseOSReleaseText(osReleaseText);
osRelease = {
id: fields.get('id') ?? '',
version: fields.get('version_id') ?? '',
};
} catch (e) {
didFailToReadOSRelease = true;
}
}
return osRelease;
}
function parseOSReleaseText(osReleaseText: string): Map<string, string> {
const fields = new Map();
for (const line of osReleaseText.split('\n')) {
const tokens = line.split('=');
const name = tokens.shift();
let value = tokens.join('=').trim();
if (value.startsWith('"') && value.endsWith('"'))
value = value.substring(1, value.length - 1);
if (!name)
continue;
fields.set(name.toLowerCase(), value);
}
return fields;
}

View File

@ -1,91 +0,0 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fs from 'fs';
import * as os from 'os';
let ubuntuVersionCached: string | undefined;
export async function getUbuntuVersion(): Promise<string> {
if (ubuntuVersionCached === undefined)
ubuntuVersionCached = await getUbuntuVersionAsyncInternal();
return ubuntuVersionCached;
}
export function getUbuntuVersionSync(): string {
if (ubuntuVersionCached === undefined)
ubuntuVersionCached = getUbuntuVersionSyncInternal();
return ubuntuVersionCached;
}
async function getUbuntuVersionAsyncInternal(): Promise<string> {
if (os.platform() !== 'linux')
return '';
let osReleaseText = await fs.promises.readFile('/etc/upstream-release/lsb-release', 'utf8').catch(e => '');
if (!osReleaseText)
osReleaseText = await fs.promises.readFile('/etc/os-release', 'utf8').catch(e => '');
if (!osReleaseText)
return '';
return parseUbuntuVersion(osReleaseText);
}
function getUbuntuVersionSyncInternal(): string {
if (os.platform() !== 'linux')
return '';
try {
let osReleaseText: string;
if (fs.existsSync('/etc/upstream-release/lsb-release'))
osReleaseText = fs.readFileSync('/etc/upstream-release/lsb-release', 'utf8');
else
osReleaseText = fs.readFileSync('/etc/os-release', 'utf8');
if (!osReleaseText)
return '';
return parseUbuntuVersion(osReleaseText);
} catch (e) {
return '';
}
}
export function parseOSReleaseText(osReleaseText: string): Map<string, string> {
const fields = new Map();
for (const line of osReleaseText.split('\n')) {
const tokens = line.split('=');
const name = tokens.shift();
let value = tokens.join('=').trim();
if (value.startsWith('"') && value.endsWith('"'))
value = value.substring(1, value.length - 1);
if (!name)
continue;
fields.set(name.toLowerCase(), value);
}
return fields;
}
function parseUbuntuVersion(osReleaseText: string): string {
const fields = parseOSReleaseText(osReleaseText);
// For Linux mint
if (fields.get('distrib_id') && fields.get('distrib_id')?.toLowerCase() === 'ubuntu')
return fields.get('distrib_release') || '';
// For Pop!_OS
if (fields.get('id') && fields.get('id')?.toLowerCase() === 'pop')
return fields.get('version_id') || '';
if (!fields.get('name') || fields.get('name')?.toLowerCase() !== 'ubuntu')
return '';
return fields.get('version_id') || '';
}