Source code for meta_package_manager.managers.winget

# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

from __future__ import annotations

import re

from extra_platforms import WINDOWS

from ..base import PackageManager

TYPE_CHECKING = False
if TYPE_CHECKING:
    from collections.abc import Generator, Iterator

    from ..base import Package


[docs] class WinGet(PackageManager): homepage_url = "https://github.com/microsoft/winget-cli" platforms = WINDOWS requirement = ">=1.28.190" post_args = ("--accept-source-agreements", "--disable-interactivity") """ ``--accept-source-agreements``: Used to accept the source license agreement, and avoid the following prompt: .. code-block:: pwsh-session PS C:\\Users\\kev> winget list The "msstore' source requires that you view the following agreements before using. Terms of Transaction: https://aka.ms/microsoft-store-terms-of-transaction The source requires the current machine's 2-letter geographic region to be sent to the backend service to function prope rly (ex. "US"). Do you agree to all the source agreements terms? [Y] Yes [N] No: ``--disable-interactivity``: Disable interactive prompts. ..todo:: Add the ``--no-progress`` option once it is available in the stable release: - https://github.com/microsoft/winget-cli/pull/6049 - https://github.com/microsoft/winget-cli/issues/3494#issuecomment-3921618377 """ version_regexes = (r"v(?P<version>\S+)",) """ .. code-block:: pwsh-session PS C:\\Users\\kev> winget --version v1.28.220 """ # Header line pattern. The ``(N/M)`` prefix is present only when multiple # packages are listed; a single result omits it. _header_re = re.compile( r"^(?:\(\d+/\d+\)\s+)?(.+)\s+\[([^\]]+)\]\s*$", ) def _parse_details( self, output: str, filter_by_source: bool = False ) -> Iterator[tuple[str, str, str, str | None]]: """Parse ``--details`` output from ``winget list``. Each package block starts with a header line and is followed by ``Key: Value`` metadata lines: .. code-block:: text (N/M) <Name> [<Id>] Version: <version> Publisher: <publisher> Origin Source: winget Available Upgrades: winget [<latest_version>] :param filter_by_source: If ``True``, only yield packages whose ``Origin Source`` is ``winget``. """ # Split output into per-package blocks on the header line. blocks = re.split( r"(?=^(?:\(\d+/\d+\)\s+)?.+\[[^\]]+\]\s*$)", output, flags=re.MULTILINE, ) for block in blocks: block = block.strip() if not block: continue lines = block.splitlines() header_match = self._header_re.match(lines[0]) if not header_match: continue name = header_match.group(1).strip() package_id = header_match.group(2).strip() # Collect key:value fields from the remaining lines. fields: dict[str, str] = {} for line in lines[1:]: if ":" in line: key, _, value = line.partition(":") fields[key.strip()] = value.strip() version = fields.get("Version") if not version: continue if filter_by_source and fields.get("Origin Source") != "winget": continue # Extract latest version from the "Available Upgrades" section. # The upgrade line looks like `` winget [1.2.3]``. latest_version = None upgrade_match = re.search( r"^Available Upgrades:\s*\n\s+\S+\s+\[([^\]]+)\]", block, flags=re.MULTILINE, ) if upgrade_match: latest_version = upgrade_match.group(1) yield name, package_id, version, latest_version def _parse_table(self, output: str) -> Iterator[Generator[str, None, None]]: """Parse a table from the output of a winget command and returns a generator of cells.""" # Extract table. table_start = "Name " if table_start not in output: return assert output.count(table_start) == 1, ( f"{table_start!r} not unique in:\n{output}" ) table = output.split(table_start, 1)[1] # Check table format. lines = table.splitlines() table_width = len(lines[0]) assert lines[1] == "-" * table_width, ( f"Table headers not followed by expected separator:\n{table}" ) assert all(len(line) <= table_width for line in lines[2:]), ( f"Table lines with different width:\n{table}" ) # Guess column positions. headers = [] col_str = "" for char in lines[0]: if col_str and char != " " and " " in col_str: headers.append(col_str) col_str = "" col_str += char if col_str: headers.append(col_str) col_ranges = [] for header in headers: start = lines[0].index(header) end = start + len(header) col_ranges.append((start, end)) for line in lines[2:]: yield (line[start:end].strip() for start, end in col_ranges) @property def installed(self) -> Iterator[Package]: """Fetch installed packages. .. code-block:: pwsh-session PS C:\\Users\\kev> winget list --details --accept-source-agreements --disable-interactivity (1/7) CCleaner [CCleaner] Version: 6.08 Publisher: Piriform Software Ltd Local Identifier: ARP\\Machine\\X64\\CCleaner Product Code: CCleaner Installer Category: exe Installed Scope: Machine Installed Architecture: X64 Installed Locale: en-US Origin Source: winget Available Upgrades: (2/7) Git [Git.Git] Version: 2.37.3 Publisher: The Git Development Community ... Only returns packages with Origin Source: winget to exclude packages installed via other sources (e.g., sideload, portable). """ output = self.run_cli("list", "--details") for name, package_id, installed_version, _ in self._parse_details( output, filter_by_source=True ): yield self.package( id=package_id, name=name, installed_version=installed_version, ) @property def outdated(self) -> Iterator[Package]: """Fetch outdated packages. .. code-block:: pwsh-session PS C:\\Users\\kev> winget list --upgrade-available --details --accept-source-agreements --disable-interactivity (1/4) Git [Git.Git] Version: 2.37.3 Publisher: The Git Development Community ... Available Upgrades: winget [2.45.1] (2/4) Microsoft Edge [Microsoft.Edge] Version: 109.0.1518.70 Publisher: Microsoft ... Available Upgrades: winget [125.0.2535.51] Only returns packages with Origin Source: winget to exclude packages installed via other sources (e.g., sideload, portable). """ output = self.run_cli("list", "--upgrade-available", "--details") for name, package_id, installed_version, latest_version in self._parse_details( output, filter_by_source=True ): yield self.package( id=package_id, name=name, installed_version=installed_version, latest_version=latest_version, )
[docs] def search(self, query: str, extended: bool, exact: bool) -> Iterator[Package]: """Fetch matching packages. .. code-block:: pwsh-session PS C:\\Users\\kev> winget search --query vscode --accept-source-agreements --disable-interactivity Name Id Version Match Source --------------------------------------------------------------------------------------------------------- Microsoft Visual Studio Code Microsoft.VisualStudioCode 1.89.1 Moniker: vscode winget MrCode zokugun.MrCode 1.82.0.23253 Tag: vscode winget VSCodium Insiders VSCodium.VSCodium.Insiders 1.88.0.24095 Tag: vscode winget VSCodium VSCodium.VSCodium 1.89.1.24130 Tag: vscode winget Upgit pluveto.Upgit 0.2.18 Tag: vscode winget vscli michidk.vscli 0.3.0 Tag: vscode winget Huawei QuickApp IDE Huawei.QuickAppIde 14.0.1 Tag: vscode winget TheiaBlueprint EclipseFoundation.TheiaBlueprint 1.44.0 Tag: vscode winget Codium Alex313031.Codium 1.86.2.24053 Tag: vscode winget Cursor Editor CursorAI,Inc.Cursor latest Tag: vscode winget Microsoft Visual Studio Code CLI Microsoft.VisualStudioCode.CLI 1.89.1 Moniker: vscode-cli winget .. code-block:: pwsh-session PS C:\\Users\\kev> winget search --query vscode --exact --accept-source-agreements --disable-interactivity Name Id Version Match Source ------------------------------------------------------------------------------------------------- Microsoft Visual Studio Code Microsoft.VisualStudioCode 1.89.1 Moniker: vscode winget MrCode zokugun.MrCode 1.82.0.23253 Tag: vscode winget VSCodium Insiders VSCodium.VSCodium.Insiders 1.88.0.24095 Tag: vscode winget VSCodium VSCodium.VSCodium 1.89.1.24130 Tag: vscode winget Upgit pluveto.Upgit 0.2.18 Tag: vscode winget vscli michidk.vscli 0.3.0 Tag: vscode winget Huawei QuickApp IDE Huawei.QuickAppIde 14.0.1 Tag: vscode winget TheiaBlueprint EclipseFoundation.TheiaBlueprint 1.44.0 Tag: vscode winget Codium Alex313031.Codium 1.86.2.24053 Tag: vscode winget Cursor Editor CursorAI,Inc.Cursor latest Tag: vscode winget .. code-block:: pwsh-session PS C:\\Users\\kev> winget search --id VSCodium.VSCodium --accept-source-agreements --disable-interactivity Name Id Version Source ---------------------------------------------------------------- VSCodium Insiders VSCodium.VSCodium.Insiders 1.88.0.24095 winget VSCodium VSCodium.VSCodium 1.89.1.24130 winget .. code-block:: pwsh-session PS C:\\Users\\kev> winget search --name Codium --accept-source-agreements --disable-interactivity Name Id Version Source ---------------------------------------------------------------- Codium Alex313031.Codium 1.86.2.24053 winget VSCodium Insiders VSCodium.VSCodium.Insiders 1.88.0.24095 winget VSCodium VSCodium.VSCodium 1.89.1.24130 winget .. code-block:: pwsh-session PS C:\\Users\\kev> winget search --id VSCodium.VSCodium --exact --accept-source-agreements --disable-interactivity Name Id Version Source ---------------------------------------------- VSCodium VSCodium.VSCodium 1.89.1.24130 winget .. code-block:: pwsh-session PS C:\\Users\\kev> winget search --name Codium --exact --accept-source-agreements --disable-interactivity Name Id Version Source -------------------------------------------- Codium Alex313031.Codium 1.86.2.24053 winget """ # Default search is extended to all metadata: id, name, moniker and tag. if extended: args = ["search", "--query", query] # Exact search deactivates substring search. if exact: args.append("--exact") output = self.run_cli(args) for name, package_id, version, _, _ in self._parse_table(output): yield self.package(id=package_id, name=name, latest_version=version) # For non-extended search, we need to perform 2 queries, one for id and # one for name. else: for field in "--id", "--name": output = self.run_cli( "search", field, query, "--exact" if exact else None ) for name, package_id, version, _ in self._parse_table(output): yield self.package(id=package_id, name=name, latest_version=version)
[docs] def install(self, package_id: str, version: str | None = None) -> str: """Install one package. .. code-block:: pwsh-session PS C:\\Users\\kev> winget install --id Microsoft.PowerToys --accept-package-agreements --version 0.15.2 --accept-source-agreements --disable-interactivity Found Power Toys [Microsoft.PowerToys] Version 0.15.2 This application is licensed to you by its owner. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. Successfully verified installer hash Starting package install... β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 100% Successfully installed """ args = ["install", "--id", package_id, "--accept-package-agreements"] if version: args += ["--version", version] return self.run_cli(args)
[docs] def upgrade_all_cli(self) -> tuple[str, ...]: """Generates the CLI to upgrade all packages (default) or only the one provided as parameter. .. code-block:: pwsh-session PS C:\\Users\\kev> winget upgrade --all --accept-package-agreements --accept-source-agreements --disable-interactivity Name Id Version Available Source ------------------------------------------------------------------------------------------------ Microsoft Edge Microsoft.Edge 109.0.1518.70 125.0.2535.51 winget Microsoft Edge WebView2 Runtime Microsoft.EdgeWebView2Runtime 109.0.1518.70 125.0.2535.51 winget Python Launcher Python.Launchez < 3.12.0 3.12.0 winget Microsoft Visual C++ (x86)... Microsoft.VCRedist.2015+.X86 14.34.31931.0 14.38.33135.0 winget 4 upgrades available. Installing dependencies: This package requires the following dependencies: - Packages Microsoft.UI.Xaml.2.8 [>= 8.2306.22001.0] (1/3) Found Microsoft Edge WebView2 Runtime [Microsoft.EdgeWebView2Runtime] Version 125. 0.2535.51 This application is licensed to you by its owner. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. Downloading https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/e5dd841e-17ff-43b7-a2c0-ff759f55c202/MicrosoftEdgeWebView2RuntimeInstallerARM64.exe β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 166 MB / 166 MB Successfully verified installer hash Starting package install... Successfully installed (...) """ return self.build_cli("update", "--all", "--accept-package-agreements")
[docs] def upgrade_one_cli( self, package_id: str, version: str | None = None ) -> tuple[str, ...]: """Generates the CLI to upgrade all packages (default) or only the one provided as parameter. .. code-block:: pwsh-session PS C:\\Users\\kev> winget upgrade --id Git.Git --accept-package-agreements --accept-source-agreements --disable-interactivity Found Git [Git.Git] Version 2.45.1 This application is licensed to you by its owner. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. Downloading https://github.com/git-for-windows/git/releases/download/v2.45.1.windows.1/Git-2.45.1-64-bit.exe β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 64.7 MB / 64.7 MB Successfully verified installer hash Starting package install... Successfully installed .. todo:: Automatically uninstall the package if the technology is different: .. code-block:: pwsh-session PS C:\\Users\\kev> winget upgrade --id Microsoft.Edge A newer version was found, but the install technology is different from the current version installed. Please uninstall the package and install the newer version. """ args = ["install", "--id", package_id, "--accept-package-agreements"] if version: args += ["--version", version] return self.build_cli(args)
[docs] def remove(self, package_id: str) -> str: """Remove one package. .. code-block:: pwsh-session PS C:\\Users\\kev> winget uninstall --id Microsoft.PowerToys --source winget --accept-source-agreements --disable-interactivity Found PowerToys (Preview) [Microsoft.PowerToys] Starting package uninstall... β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 100% Successfully uninstalled """ return self.run_cli("uninstall", "--id", package_id, "--source", "winget")
[docs] def sync(self) -> None: """Sync package metadata from remote sources. .. code-block:: pwsh-session PS C:\\Users\\kev> winget source update --accept-source-agreements --disable-interactivity """ self.run_cli("source", "update")