Compare commits

...

6 Commits

Author SHA1 Message Date
dengbo f3ce211187 Apply suggestion from @gemini-code-assist[bot]
build / ubuntu_24.04 (push) Has been cancelled Details
build / ubuntu_22.04 (push) Has been cancelled Details
coverage / codecov (push) Has been cancelled Details
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-11-07 15:50:48 +08:00
reddevillg e1a6509752 refactor: Refactor installation logic into Action classes
Encapsulates the complex package installation process into a set of
Action classes to improve clarity, maintainability, and testability.

This change introduces an abstract `Action` base class and a concrete
`RefInstallationAction` and `UabInstallationAction` for handling
installations from a repository or UAB file.
The core logic, including remote package discovery, version comparison,
user interaction, and transaction management, has been moved from
PackageManager into concrete installation action.

Key changes:
- A new `RefInstallationAction` class now manages the entire lifecycle
  of a reference-based installation task.
- `UabInstallationAction` class now is derived from `Action`.
- A `RemotePackages` helper class is introduced to manage and query
  package search results from multiple repositories.
- `OSTreeRepo` is extended with a `matchRemoteByPriority` method to
  find packages according to repository priorities.
- The `PackageManager::Install` method is simplified to be a factory
  for creating and queuing installation tasks.
- Added extensive unit tests for the new remote package matching logic.

Signed-off-by: reddevillg <reddevillg@gmail.com>
2025-11-07 15:50:48 +08:00
dengbo bd80b77f50 fix: Refactors logging system initialization to use explicit backend
Replaces command-based initialization of the logging system with
an explicit backend parameter, simplifying configuration logic
and improving maintainability.

Removes redundant code that inferred the log backend from
command names. Updates all affected components to pass the
appropriate backend directly when initializing the log system.
2025-11-07 14:20:57 +08:00
deepsource-autofix[bot] c77b8263c0 style: format code with ClangFormat and Prettier
This commit fixes the style issues introduced in c094c2a according to the output
from ClangFormat and Prettier.

Details: None
2025-11-07 12:59:15 +08:00
dengbo c094c2ab21 docs: Remove deprecated files and update documentation links
Removes outdated documentation files related to installation and conversion tools. Updates broken or incorrect links in multiple documentation files to point to the correct targets. Introduces new `ll-pica-manifests.md` for explaining conversion configuration details. Simplifies redundant sections and ensures consistency across both English and Chinese documentation.

Improves clarity and accessibility of documentation while removing obsolete references.
2025-11-07 11:28:44 +08:00
dengbo f0cb94dbc8 refactor: Simplifies icon packaging logic in UABPackager
Refactors the icon packaging process by removing the intermediate
step of creating an "icon.a" archive and directly passing the
icon value to the `addNewSection` method. Improves code
clarity and reduces unnecessary operations.
2025-11-07 10:55:55 +08:00
43 changed files with 1521 additions and 798 deletions

View File

@ -183,7 +183,6 @@ For assistance, use the following channels:
- [Init](./docs/pages/en/guide/reference/commands/ll-pica/ll-pica-init.md)
- [Convert](./docs/pages/en/guide/reference/commands/ll-pica/ll-pica-convert.md)
- [Adep](./docs/pages/en/guide/reference/commands/ll-pica/ll-pica-adep.md)
- [Install](./docs/pages/en/guide/reference/commands/ll-pica/install.md)
#### AppImage Conversion

View File

@ -185,7 +185,6 @@ ll-cli run cn.org.linyaps.demo
- [初始化](./docs/pages/guide/reference/commands/ll-pica/ll-pica-init.md)
- [转换](./docs/pages/guide/reference/commands/ll-pica/ll-pica-convert.md)
- [依赖](./docs/pages/guide/reference/commands/ll-pica/ll-pica-adep.md)
- [安装](./docs/pages/guide/reference/commands/ll-pica/install.md)
#### AppImage 包转换

View File

@ -740,7 +740,7 @@ int main(int argc, char **argv)
Q_INIT_RESOURCE(builder_releases);
// 初始化应用builder在非tty环境也输出日志
linglong::utils::global::applicationInitialize(true);
linglong::utils::global::initLinyapsLogSystem(argv[0]);
linglong::utils::global::initLinyapsLogSystem(linglong::utils::log::LogBackend::Console);
CLI::App commandParser{ _("linyaps builder CLI \n"
"A CLI program to build linyaps application\n") };

View File

@ -930,7 +930,7 @@ int main(int argc, char **argv)
QCoreApplication app(argc, argv);
// application initialize
applicationInitialize();
initLinyapsLogSystem(argv[0]);
initLinyapsLogSystem(linglong::utils::log::LogBackend::Journal);
// invoke method
auto ret = QMetaObject::invokeMethod(

View File

@ -151,7 +151,7 @@ auto main(int argc, char *argv[]) -> int
QCoreApplication app(argc, argv);
applicationInitialize();
initLinyapsLogSystem(argv[0]);
initLinyapsLogSystem(linglong::utils::log::LogBackend::Journal);
auto ociRuntimeCLI = qgetenv("LINGLONG_OCI_RUNTIME");
if (ociRuntimeCLI.isEmpty()) {

View File

@ -174,11 +174,11 @@ docs/pages/en/guide/
### Command Reference
- Build Tool: [`reference/commands/ll-builder/`](reference/commands/ll-builder/) directory
- CLI Tool: [`reference/commands/ll-cli/`](reference/commands/ll-cli/) directory
- Deb Package Conversion Tool: [`reference/commands/ll-pica/`](reference/commands/ll-pica/) directory
- AppImage Conversion Tool: [`reference/commands/ll-appimage-convert/`](reference/commands/ll-appimage-convert/) directory
- Flatpak Conversion Tool: [`reference/commands/ll-pica-flatpak/`](reference/commands/ll-pica-flatpak/) directory
- Build Tool: [`reference/commands/ll-builder/`](reference/commands/ll-builder/ll-builder.md) directory
- CLI Tool: [`reference/commands/ll-cli/`](reference/commands/ll-cli/ll-cli.md) directory
- Deb Package Conversion Tool: [`reference/commands/ll-pica/`](reference/commands/ll-pica/ll-pica.md) directory
- AppImage Conversion Tool: [`reference/commands/ll-appimage-convert/`](reference/commands/ll-appimage-convert/ll-appimage-convert.md) directory
- Flatpak Conversion Tool: [`reference/commands/ll-pica-flatpak/`](reference/commands/ll-pica-flatpak/ll-pica-flatpak.md) directory
### Common Issues

View File

@ -38,7 +38,7 @@ ll-cli is a package manager frontend for managing Linyaps application installati
| --------- | ------------------------------------- | --------------------------------------------------------------------------------------- |
| run | [ll-cli-run(1)](./run.md) | Run applications |
| ps | [ll-cli-ps(1)](./ps.md) | List running applications |
| enter | [ll-cli-exec(1)](./exec.md) | Enter the namespace of a running application |
| enter | [ll-cli-exec(1)](./enter.md) | Enter the namespace of a running application |
| kill | [ll-cli-kill(1)](./kill.md) | Stop running applications |
| prune | [ll-cli-prune(1)](./prune.md) | Remove unused base systems or runtimes |
| install | [ll-cli-install(1)](./install.md) | Install applications or runtimes |
@ -52,7 +52,7 @@ ll-cli is a package manager frontend for managing Linyaps application installati
## SEE ALSO
**[ll-cli-run(1)](./run.md)**, **[ll-cli-ps(1)](./ps.md)**, **[ll-cli-exec(1)](./exec.md)**, **[ll-cli-kill(1)](./kill.md)**, **[ll-cli-prune(1)](./prune.md)**, **[ll-cli-install(1)](./install.md)**, **[ll-cli-uninstall(1)](./uninstall.md)**, **[ll-cli-upgrade(1)](./upgrade.md)**, **[ll-cli-list(1)](./list.md)**, **[ll-cli-info(1)](./info.md)**, **[ll-cli-content(1)](./content.md)**, **[ll-cli-search(1)](./search.md)**, **[ll-cli-repo(1)](./repo.md)**
**[ll-cli-run(1)](./run.md)**, **[ll-cli-ps(1)](./ps.md)**, **[ll-cli-exec(1)](./enter.md)**, **[ll-cli-kill(1)](./kill.md)**, **[ll-cli-prune(1)](./prune.md)**, **[ll-cli-install(1)](./install.md)**, **[ll-cli-uninstall(1)](./uninstall.md)**, **[ll-cli-upgrade(1)](./upgrade.md)**, **[ll-cli-list(1)](./list.md)**, **[ll-cli-info(1)](./info.md)**, **[ll-cli-content(1)](./content.md)**, **[ll-cli-search(1)](./search.md)**, **[ll-cli-repo(1)](./repo.md)**
## HISTORY

View File

@ -64,7 +64,7 @@ ll-cli run org.deepin.demo -- bash -x /path/to/bash/script
## SEE ALSO
**[ll-cli(1)](./ll-cli.md)**, **[ll-cli-ps(1)](./ps.md)**, **[ll-cli-exec(1)](./exec.md)**
**[ll-cli(1)](./ll-cli.md)**, **[ll-cli-ps(1)](./ps.md)**, **[ll-cli-exec(1)](./enter.md)**
## HISTORY

View File

@ -66,7 +66,7 @@ ll-pica init -a "arm64"
## NOTES
After the package.yaml file is generated, users can modify it as needed. For detailed fields, refer to: [Conversion Configuration File Introduction](./manifests.md).
After the package.yaml file is generated, users can modify it as needed. For detailed fields, refer to: [Conversion Configuration File Introduction](./ll-pica-manifests.md).
## SEE ALSO

View File

@ -0,0 +1,57 @@
# Conversion Configuration File Introduction
`package.yaml` is the basic information file for `ll-pica` to convert deb packages. It contains information such as the base and runtime versions to be built, and the deb packages that need to be converted.
## Project Directory Structure
```bash
{workdir}
├── package
│ └── {appid}
│ ├── linglong
│ ├── linglong.yaml
│ └── start.sh
└── package.yaml
```
## Field Definitions
### Build Environment
The build environment for converting deb packages to Linglong packages.
```bash
runtime:
version: 23.0.1
base_version: 23.0.0
source: https://community-packages.deepin.com/beige/
distro_version: beige
arch: amd64
```
| Name | Description |
| -------------- | ------------------------------------------------------------------------- |
| runtime | Runtime environment |
| version | Runtime version, three-digit version can fuzzy match the fourth digit |
| base_version | Base version number, three-digit version can fuzzy match the fourth digit |
| source | Source used when obtaining deb package dependencies |
| distro_version | Distribution code name |
| arch | Architecture required for obtaining deb packages |
### Deb Package Information
```bash
file:
deb:
- type: local
id: com.baidu.baidunetdisk
name: com.baidu.baidunetdisk
ref: /tmp/com.baidu.baidunetdisk_4.17.7_amd64.deb
```
| Name | Description |
| ---- | ---------------------------------------------------------------------------------------- |
| type | Acquisition method, local requires specifying ref, repo does not require specifying ref. |
| id | Unique name of the build product |
| name | Specify the correct package name that apt can search for |
| ref | Path of the deb package on the host machine |

View File

@ -85,7 +85,7 @@ build: |
The linglong.yaml file follows YAML syntax specifications.
For detailed explanation of fields in linglong.yaml, refer to: [Build Configuration File Introduction](../ll-builder/manifests.md)
For detailed explanation of fields in linglong.yaml, refer to: [Build Configuration File Introduction](../building/manifests.md)
## Build

View File

@ -18,15 +18,15 @@ linyaps is composed of three parts:
Automatically built based on the latest tag
1. Repository address <https://ci.deepin.com/repo/obs/linglong:/CI:/release>
2. Build address <https://build.deepin.com/project/show/linglong:CI:release>
1. Repository address <https://ci.deepin.com/repo/obs/linglong:/CI:/release>
2. Build address <https://build.deepin.com/project/show/linglong:CI:release>
### latest repository
Automatically built based on the latest commit
1. Repository address <https://ci.deepin.com/repo/obs/linglong:/CI:/latest>
2. Build address <https://build.deepin.com/project/show/linglong:CI:latest>
1. Repository address <https://ci.deepin.com/repo/obs/linglong:/CI:/latest>
2. Build address <https://build.deepin.com/project/show/linglong:CI:latest>
:::tip
@ -162,3 +162,27 @@ sudo apt install linglong-builder
```bash
sudo dnf install linglong-builder
```
## Linyaps Conversion Tool Installation Instructions
### Deepin 23/25
```bash
sudo apt install linglong-pica
```
### Arch Linux
Install via [AUR repository](https://aur.archlinux.org/packages/linglong-pica) or [self-hosted repository](https://github.com/taotieren/aur-repo).
```bash
# AUR
yay -Syu linglong-pica
# or self-hosted repository
sudo pacman -Syu linglong-pica
```

View File

@ -1,28 +0,0 @@
# Linyaps Conversion Tool (pica) Installation
This tool provides the ability to convert deb, appimage, and flatpak packages to Linyaps packages, generating the linglong.yaml file needed to build Linyaps applications, and relies on ll-builder to implement application building and export.
## deepin v23
```bash
sudo apt install linglong-pica
```
## UOS 1070
```bash
echo "deb [trusted=yes] https://ci.deepin.com/repo/deepin/deepin-community/linglong-repo/ unstable main" | sudo tee -a /etc/apt/sources.list
sudo apt update
sudo apt install linglong-pica
```
## Arch Linux
Install through [AUR repository](https://aur.archlinux.org/packages/linglong-pica) or [self-built repository](https://github.com/taotieren/aur-repo).
```bash
# AUR
yay -Syu linglong-pica
# Or self-built repository
sudo pacman -Syu linglong-pica
```

View File

@ -172,11 +172,11 @@ docs/pages/guide/
### 命令参考
- 构建工具: [`reference/commands/ll-builder/`](reference/commands/ll-builder/) 目录
- 命令行工具: [`reference/commands/ll-cli/`](reference/commands/ll-cli/) 目录
- deb包转换工具: [`reference/commands/ll-pica/`](reference/commands/ll-pica/) 目录
- AppImage转换工具: [`reference/commands/ll-appimage-convert/`](reference/commands/ll-appimage-convert/) 目录
- Flatpak转换工具: [`reference/commands/ll-pica-flatpak/`](reference/commands/ll-pica-flatpak/) 目录
- 构建工具: [`reference/commands/ll-builder/`](reference/commands/ll-builder/ll-builder.md) 目录
- 命令行工具: [`reference/commands/ll-cli/`](reference/commands/ll-cli/ll-cli.md) 目录
- deb包转换工具: [`reference/commands/ll-pica/`](reference/commands/ll-pica/ll-pica.md) 目录
- AppImage转换工具: [`reference/commands/ll-appimage-convert/`](reference/commands/ll-appimage-convert/ll-appimage-convert.md) 目录
- Flatpak转换工具: [`reference/commands/ll-pica-flatpak/`](reference/commands/ll-pica-flatpak/ll-pica-flatpak.md) 目录
### 常见问题

View File

@ -1,88 +0,0 @@
<!--
SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
SPDX-License-Identifier: LGPL-3.0-or-later
-->
# 转换AppImage应用
转换 `AppImage` 包格式( `.AppImage``.AppImage` ) 到如意玲珑包格式( `.layer``.uab` )
查看 `ll-appimage-convert convert` 命令的帮助信息:
```bash
ll-appimage-convert convert --help
```
`ll-appimage-convert convert` 命令的帮助信息如下:
```text
Usage:
ll-appimage-convert convert [flags]
Flags:
-b, --build build linglong
-d, --description string detailed description of the package
-f, --file string app package file, it is not required option,
you can ignore this option
when you set --url option and --hash option
--hash string pkg hash value, it must be used with --url option
-h, --help help for convert
-i, --id string the unique name of the package
-l, --layer export layer file
-n, --name string the description the package
-u, --url string pkg url, it is not required option, you can ignore this option when you set -f option
-v, --version string the version of the package
Global Flags:
-V, --verbose verbose output
```
`ll-appimage-convert convert` 命令会根据指定的应用名称( `--name` 选项)生成一个目录,该目录会作为如意玲珑项目的根目录,即 `linglong.yaml` 文件所在的位置。它支持两种转换方法:
1. 你可以使用 `--file` 选项将指定的 `AppImage` 文件转换为如意玲珑包文件;
2. 你可以使用 `--url``--hash` 选项将指定的 `AppImage url``hash` 值转换为如意玲珑包文件;
3. 你可以使用 `--layer` 选项导出 `.layer` 格式文件,否则将默认导出 `.uab` 格式文件。
`Tips: 在如意玲珑版本大于1.5.7时convert 默认导出 uab 包,如果想要导出 layer 文件,需要加上 --layer 参数`
你可以使用 `--output` 选项生成如意玲珑项目的配置文件 (`linglong.yaml`) 和构建如意玲珑 `.layer` (`.uab`) 的脚本文件
然后你可以执行该脚本去生成对应的如意玲珑包当你修改 `linglong.yaml` 配置文件后。如果不指定该选项,将直接导出对应的如意玲珑包。
以通过 `--url` 选项将 [BrainWaves](https://github.com/makebrainwaves/BrainWaves/releases/download/v0.15.1/BrainWaves-0.15.1.AppImage) `AppImage` 文件转换为如意玲珑 `.layer` 文件为例,主要步骤如下:
指定要转换的如意玲珑包的相关参数,稍等片刻后你就可以得到 `io.github.brainwaves_0.15.1.0_x86_64_runtime.layer` 或者 `io.github.brainwaves_0.15.1.0_x86_64_runtime.uab` 包文件。
```bash
ll-appimage-convert convert --url "https://github.com/makebrainwaves/BrainWaves/releases/download/v0.15.1/BrainWaves-0.15.1.AppImage" --hash "04fcfb9ccf5c0437cd3007922fdd7cd1d0a73883fd28e364b79661dbd25a4093" --name "io.github.brainwaves" --id "io.github.brainwaves" --version "0.15.1.0" --description "io.github.brainwaves" -b
```
以通过 `--file` 选项将 `BrainWaves-0.15.1.AppImage` 转换为如意玲珑 `.uab` 包为例,主要步骤如下:
```bash
ll-appimage-convert convert -f ~/Downloads/BrainWaves-0.15.1.AppImage --name "io.github.brainwaves" --id "io.github.brainwaves" --version "0.15.1.0" --description "io.github.brainwaves" -b
```
转换完成的目录结构如下:
```text
├── io.github.brainwaves_x86_64_0.15.1.0_main.uab
├── linglong
└── linglong.yaml
```
以通过 `--file` 选项将 `BrainWaves-0.15.1.AppImage` 转换为如意玲珑 `.layer` 包为例,主要步骤如下:
```bash
ll-appimage-convert convert -f ~/Downloads/BrainWaves-0.15.1.AppImage --name "io.github.brainwaves" --id "io.github.brainwaves" --version "0.15.1.0" --description "io.github.brainwaves" -b --layer
```
转换完成的目录结构如下:
```text
├── io.github.brainwaves_0.15.1.0_x86_64_binary.layer
├── io.github.brainwaves_0.15.1.0_x86_64_develop.layer
├── linglong
└── linglong.yaml
```
`.uab``.layer` 文件验证:
导出的 `.uab` 或者 `.layer` 需要安装后进行验证,安装 layer 文件和运行应用参考:[安装应用](../ll-cli/install.md)

View File

@ -38,7 +38,7 @@ ll-cli 是一个包管理器前端,用于管理如意玲珑应用的安装、
| --------- | ------------------------------------- | ----------------------------------------------- |
| run | [ll-cli-run(1)](./run.md) | 运行应用程序 |
| ps | [ll-cli-ps(1)](./ps.md) | 列出正在运行的应用程序 |
| enter | [ll-cli-exec(1)](./exec.md) | 进入应用程序正在运行的命名空间 |
| enter | [ll-cli-exec(1)](./enter.md) | 进入应用程序正在运行的命名空间 |
| kill | [ll-cli-kill(1)](./kill.md) | 停止运行的应用程序 |
| prune | [ll-cli-prune(1)](./prune.md) | 移除未使用的最小系统或运行时 |
| install | [ll-cli-install(1)](./install.md) | 安装应用程序或运行时 |
@ -52,7 +52,7 @@ ll-cli 是一个包管理器前端,用于管理如意玲珑应用的安装、
## SEE ALSO
**[ll-cli-run(1)](./run.md)**, **[ll-cli-ps(1)](./ps.md)**, **[ll-cli-exec(1)](./exec.md)**, **[ll-cli-kill(1)](./kill.md)**, **[ll-cli-prune(1)](./prune.md)**, **[ll-cli-install(1)](./install.md)**, **[ll-cli-uninstall(1)](./uninstall.md)**, **[ll-cli-upgrade(1)](./upgrade.md)**, **[ll-cli-list(1)](./list.md)**, **[ll-cli-info(1)](./info.md)**, **[ll-cli-content(1)](./content.md)**, **[ll-cli-search(1)](./search.md)**, **[ll-cli-repo(1)](./repo.md)**
**[ll-cli-run(1)](./run.md)**, **[ll-cli-ps(1)](./ps.md)**, **[ll-cli-exec(1)](./enter.md)**, **[ll-cli-kill(1)](./kill.md)**, **[ll-cli-prune(1)](./prune.md)**, **[ll-cli-install(1)](./install.md)**, **[ll-cli-uninstall(1)](./uninstall.md)**, **[ll-cli-upgrade(1)](./upgrade.md)**, **[ll-cli-list(1)](./list.md)**, **[ll-cli-info(1)](./info.md)**, **[ll-cli-content(1)](./content.md)**, **[ll-cli-search(1)](./search.md)**, **[ll-cli-repo(1)](./repo.md)**
## HISTORY

View File

@ -64,7 +64,7 @@ ll-cli run org.deepin.demo -- bash -x /path/to/bash/script
## SEE ALSO
**[ll-cli(1)](./ll-cli.md)**, **[ll-cli-ps(1)](./ps.md)**, **[ll-cli-exec(1)](./exec.md)**
**[ll-cli(1)](./ll-cli.md)**, **[ll-cli-ps(1)](./ps.md)**, **[ll-cli-exec(1)](./enter.md)**
## HISTORY

View File

@ -1,28 +0,0 @@
# 玲珑转换工具(pica)安装
本工具提供 deb、appimage、flatpak包转换为如意玲珑包的能力生成构建如意玲珑应用需要的 linglong.yaml 文件,并依赖 ll-builder 来实现应用构建和导出。
## deepin v23
```bash
sudo apt install linglong-pica
```
## UOS 1070
```bash
echo "deb [trusted=yes] https://ci.deepin.com/repo/deepin/deepin-community/linglong-repo/ unstable main" | sudo tee -a /etc/apt/sources.list
sudo apt update
sudo apt install linglong-pica
```
## Arch Linux
通过 [AUR 仓库](https://aur.archlinux.org/packages/linglong-pica) 或 [自建源仓库](https://github.com/taotieren/aur-repo) 安装。
```bash
# AUR
yay -Syu linglong-pica
# 或自建源
sudo pacman -Syu linglong-pica
```

View File

@ -66,7 +66,7 @@ ll-pica init -a "arm64"
## NOTES
package.yaml 文件生成后,用户可以根据需要进行修改。详细字段参考:[转换配置文件简介](./manifests.md)。
package.yaml 文件生成后,用户可以根据需要进行修改。详细字段参考:[转换配置文件简介](./ll-pica-manifests.md)。
## SEE ALSO

View File

@ -0,0 +1,57 @@
# 转换配置文件简介
package.yaml 是 `ll-pica` 转换 deb 包的基础信息。如构建的 base、runtime 的版本,需要被转换的 deb 包。
## 工程目录结构
```bash
{workdir}
├── package
│ └── {appid}
│ ├── linglong
│ ├── linglong.yaml
│ └── start.sh
└── package.yaml
```
## 字段定义
### 构建环境
deb 包转玲珑包的构建环境。
```bash
runtime:
version: 23.0.1
base_version: 23.0.0
source: https://community-packages.deepin.com/beige/
distro_version: beige
arch: amd64
```
| 名称 | 描述 |
| -------------- | ----------------------------------------------- |
| runtime | 运行时runtime |
| version | 运行时runtime版本三位数可以模糊匹配第四位 |
| base_version | base 的版本号, 三位数可以模糊匹配第四位 |
| source | 获取 deb 包依赖时使用的源 |
| distro_version | 发行版的代号 |
| arch | 获取 deb 包需要的架构 |
### deb 包信息
```bash
file:
deb:
- type: local
id: com.baidu.baidunetdisk
name: com.baidu.baidunetdisk
ref: /tmp/com.baidu.baidunetdisk_4.17.7_amd64.deb
```
| 名称 | 描述 |
| ---- | ----------------------------------------------------- |
| type | 获取的方式local 需要指定 refrepo 不需要指定 ref。 |
| id | 构建产物的唯一名称 |
| name | 指定 apt 能搜索到的正确包名 |
| ref | deb 包在宿主机的路径 |

View File

@ -85,7 +85,7 @@ build: |
linglong.yaml 文件遵循 yaml 语法规范。
linglong.yaml 中字段的详细解释参考:[构建配置文件简介](../ll-builder/manifests.md)
linglong.yaml 中字段的详细解释参考:[构建配置文件简介](../building/manifests.md)
## 构建

View File

@ -18,15 +18,15 @@ SPDX-License-Identifier: LGPL-3.0-or-later
基于最新tag自动构建
1. 仓库地址 <https://ci.deepin.com/repo/obs/linglong:/CI:/release>
2. 构建地址 <https://build.deepin.com/project/show/linglong:CI:release>
1. 仓库地址 <https://ci.deepin.com/repo/obs/linglong:/CI:/release>
2. 构建地址 <https://build.deepin.com/project/show/linglong:CI:release>
### latest 仓库
基于最新提交自动构建
1. 仓库地址 <https://ci.deepin.com/repo/obs/linglong:/CI:/latest>
2. 构建地址 <https://build.deepin.com/project/show/linglong:CI:latest>
1. 仓库地址 <https://ci.deepin.com/repo/obs/linglong:/CI:/latest>
2. 构建地址 <https://build.deepin.com/project/show/linglong:CI:latest>
:::tip
@ -162,3 +162,22 @@ sudo apt install linglong-builder
```bash
sudo dnf install linglong-builder
```
## 如意玲珑转换工具安装说明
### Deepin 23/25
```bash
sudo apt install linglong-pica
```
### Arch Linux
通过 [AUR 仓库](https://aur.archlinux.org/packages/linglong-pica) 或 [自建源仓库](https://github.com/taotieren/aur-repo) 安装。
```bash
# AUR
yay -Syu linglong-pica
# 或自建源
sudo pacman -Syu linglong-pica
```

View File

@ -49,10 +49,14 @@ pfl_add_library(
src/linglong/package/layer_file.h
src/linglong/package/layer_packager.cpp
src/linglong/package/layer_packager.h
src/linglong/package_manager/action.cpp
src/linglong/package_manager/action.h
src/linglong/package_manager/package_manager.cpp
src/linglong/package_manager/package_manager.h
src/linglong/package_manager/package_task.cpp
src/linglong/package_manager/package_task.h
src/linglong/package_manager/ref_installation.cpp
src/linglong/package_manager/ref_installation.h
src/linglong/package_manager/uab_installation.cpp
src/linglong/package_manager/uab_installation.h
src/linglong/package/reference.cpp
@ -76,6 +80,8 @@ pfl_add_library(
src/linglong/repo/migrate.h
src/linglong/repo/ostree_repo.cpp
src/linglong/repo/ostree_repo.h
src/linglong/repo/remote_packages.cpp
src/linglong/repo/remote_packages.h
src/linglong/repo/repo_cache.cpp
src/linglong/repo/repo_cache.h
src/linglong/runtime/container_builder.cpp

View File

@ -281,14 +281,8 @@ utils::error::Result<void> UABPackager::packIcon() noexcept
{
LINGLONG_TRACE("add icon to uab")
auto iconAchieve = this->uab.parentDir().absoluteFilePath("icon.a");
if (auto ret = utils::command::Cmd("ar").exec({ "q", iconAchieve, icon->absoluteFilePath() });
!ret) {
return LINGLONG_ERR(ret);
}
QByteArray iconSection{ "linglong.icon" };
if (auto ret = this->uab.addNewSection(iconSection, QFileInfo{ iconAchieve }); !ret) {
if (auto ret = this->uab.addNewSection(iconSection, icon.value()); !ret) {
return LINGLONG_ERR(ret);
}

View File

@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "action.h"
#include "linglong/repo/ostree_repo.h"
#include "linglong/repo/repo_cache.h"
namespace linglong::service {
utils::error::Result<ActionOperation>
Action::getActionOperation(const api::types::v1::PackageInfoV2 &target, bool extraModuleOnly)
{
LINGLONG_TRACE("get action operation");
ActionOperation action;
auto targetRef = package::Reference::fromPackageInfo(target);
if (!targetRef) {
return LINGLONG_ERR(targetRef);
}
if (extraModuleOnly) {
action.operation = ActionOperation::Install;
} else {
repo::repoCacheQuery query;
query.channel = target.channel;
query.id = target.id;
query.deleted = false;
auto localRefs = repo.listLocalBy(query);
if (!localRefs) {
return LINGLONG_ERR(localRefs);
}
if (!localRefs->empty()) {
// localRefs is sort by version
auto oldRef = package::Reference::fromPackageInfo(localRefs->front().info);
if (!oldRef) {
return LINGLONG_ERR(oldRef);
}
if (targetRef->version > oldRef->version) {
action.operation = ActionOperation::Upgrade;
} else if (targetRef->version < oldRef->version) {
action.operation = ActionOperation::Downgrade;
} else {
action.operation = ActionOperation::Overwrite;
}
// see if the same version is installed
for (auto it = localRefs->begin() + 1; it != localRefs->end(); ++it) {
auto ref = package::Reference::fromPackageInfo(it->info);
if (!ref) {
return LINGLONG_ERR(ref);
}
if (ref->version == targetRef->version) {
action.operation = ActionOperation::Overwrite;
oldRef = std::move(ref);
break;
}
}
// multiple version can be installed for non-app package
if (action.operation != ActionOperation::Overwrite && target.kind != "app") {
action.operation = ActionOperation::Install;
}
if (action.operation != ActionOperation::Install) {
action.oldRef = std::move(oldRef).value();
}
} else {
action.operation = ActionOperation::Install;
}
}
action.kind = target.kind;
action.newRef = package::ReferenceWithRepo{
.reference = std::move(targetRef).value(),
};
return action;
}
} // namespace linglong::service

View File

@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once
#include "linglong/api/types/v1/CommonOptions.hpp"
#include "linglong/package/reference.h"
namespace linglong::repo {
class OSTreeRepo;
}
namespace linglong::service {
class PackageManager;
class PackageTask;
struct ActionOperation
{
enum Policy {
Upgrade,
Install,
Remove,
Overwrite,
Downgrade,
} operation;
std::string kind;
std::optional<package::Reference> oldRef;
std::optional<package::ReferenceWithRepo> newRef;
};
class Action
{
public:
Action(PackageManager &pm, repo::OSTreeRepo &repo, api::types::v1::CommonOptions options)
: pm(pm)
, repo(repo)
, options(options)
{
}
virtual ~Action() = default;
// prepare runs in DBus calling context, so it should not do long time operations
virtual utils::error::Result<void> prepare() = 0;
// doAction runs in PM tasks context, it's application's main loop for now
virtual utils::error::Result<void> doAction(PackageTask &task) = 0;
virtual std::string getTaskName() const = 0;
protected:
utils::error::Result<ActionOperation>
getActionOperation(const api::types::v1::PackageInfoV2 &target, bool extraModuleOnly);
PackageManager &pm;
repo::OSTreeRepo &repo;
api::types::v1::CommonOptions options;
};
} // namespace linglong::service

View File

@ -14,12 +14,14 @@
#include "linglong/api/types/v1/Repo.hpp"
#include "linglong/api/types/v1/State.hpp"
#include "linglong/common/dir.h"
#include "linglong/common/strings.h"
#include "linglong/extension/extension.h"
#include "linglong/package/layer_file.h"
#include "linglong/package/layer_packager.h"
#include "linglong/package/reference.h"
#include "linglong/package/uab_file.h"
#include "linglong/package_manager/package_task.h"
#include "linglong/package_manager/ref_installation.h"
#include "linglong/package_manager/uab_installation.h"
#include "linglong/repo/config.h"
#include "linglong/repo/ostree_repo.h"
@ -785,9 +787,13 @@ QVariantMap PackageManager::installFromUAB(const QDBusUnixFileDescriptor &fd,
auto taskRet = tasks.addNewTask(
{ action->getTaskName() },
[action](PackageTask &task) {
LINGLONG_TRACE("uab installation task")
auto res = action->doAction(task);
if (!res) {
LogD("uab installation failed: {}", res.error());
LogE("uab installation failed: {}", res.error());
task.reportError(LINGLONG_ERRV(std::move(res).error().message(),
utils::error::ErrorCode::AppInstallFailed));
}
},
connection());
@ -842,213 +848,57 @@ auto PackageManager::Install(const QVariantMap &parameters) noexcept -> QVariant
return toDBusReply(utils::error::ErrorCode::AppInstallFailed, paras.error().message());
}
api::types::v1::PackageManager1Package package;
package.id = paras->package.id;
package.channel = paras->package.channel;
package.version = paras->package.version;
// 解析用户输入
auto fuzzyRef = fuzzyReferenceFromPackage(package);
const auto &package = paras->package;
auto fuzzyRef =
package::FuzzyReference::create(package.channel, package.id, package.version, std::nullopt);
if (!fuzzyRef) {
return toDBusReply(utils::error::ErrorCode::AppInstallFailed, fuzzyRef.error().message());
}
std::string curModule = "binary";
// install binary module by default
auto modules = package.modules.value_or(std::vector<std::string>{ "binary" });
if (paras->package.modules && paras->package.modules->size() == 1) {
// Manually install single module
curModule = paras->package.modules->front();
std::optional<repo::Repo> usedRepo;
if (paras->repo) {
auto repo = this->repo.getRepoByAlias(*paras->repo);
if (!repo) {
return toDBusReply(utils::error::ErrorCode::AppInstallFailed, repo.error().message());
}
usedRepo = std::move(repo).value();
}
auto modules = paras->package.modules.value_or(std::vector<std::string>{ curModule });
LogI("install {} {} from {}",
fuzzyRef->toString(),
common::strings::join(modules),
usedRepo ? usedRepo->name : "(all)");
// 安装module
if (curModule != "binary") {
// 安装module必须是和binary相同的版本所以不允许指定
if (fuzzyRef->version) {
return toDBusReply(utils::error::ErrorCode::AppInstallModuleNoVersion,
"cannot specify a version when installing a module");
}
auto ret = tasks.addNewTask(
{ fuzzyRef->toString() },
[this, curModule, fuzzyRef = std::move(*fuzzyRef), repo = paras->repo](
PackageTask &taskRef) {
LINGLONG_TRACE("install module")
auto localRef = this->repo.clearReference(fuzzyRef, { .fallbackToRemote = false });
if (!localRef.has_value()) {
taskRef.reportError(
LINGLONG_ERRV("to install the module, one must first install the app",
utils::error::ErrorCode::AppInstallModuleRequireAppFirst));
return;
}
auto modules = this->repo.getModuleList(*localRef);
if (std::find(modules.begin(), modules.end(), curModule) != modules.end()) {
taskRef.reportError(
LINGLONG_ERRV("module is already installed",
utils::error::ErrorCode::AppInstallModuleAlreadyExists));
return;
}
std::optional<api::types::v1::Repo> remoteRepo;
if (repo) {
auto repoRet = this->repo.getRepoByAlias(*repo);
if (!repoRet) {
taskRef.reportError(
LINGLONG_ERRV("failed to get repo by alias", repoRet.error()));
return;
}
remoteRepo = *repoRet;
}
this->Install(taskRef, *localRef, std::nullopt, std::vector{ curModule }, remoteRepo);
},
connection());
if (!ret) {
return toDBusReply(utils::error::ErrorCode::AppInstallFailed, ret.error().message());
}
auto &taskRef = ret->get();
Q_EMIT TaskAdded(QDBusObjectPath{ taskRef.taskObjectPath() });
taskRef.updateState(linglong::api::types::v1::State::Queued,
"queued to install from remote");
return utils::serialize::toQVariantMap(api::types::v1::PackageManager1PackageTaskResult{
.taskObjectPath = taskRef.taskObjectPath().toStdString(),
.code = 0,
.message = "installing",
});
auto action = RefInstallationAction::create(*fuzzyRef,
modules,
*this,
repo,
paras->options,
std::move(usedRepo));
if (!action) {
return toDBusReply(utils::error::ErrorCode::AppInstallFailed, "");
}
// 如果用户输入了版本号,检查本地是否已经安装此版本
if (fuzzyRef->version) {
auto ref = this->repo.clearReference(*fuzzyRef,
{
.fallbackToRemote = false // NOLINT
});
if (ref) {
return toDBusReply(utils::error::ErrorCode::AppInstallAlreadyInstalled,
ref->toString() + " is already installed.");
}
auto res = action->prepare();
if (!res) {
return toDBusReply(utils::error::ErrorCode::AppInstallFailed, res.error().message());
}
// we need latest local reference
auto originalVersion = fuzzyRef->version;
fuzzyRef->version.reset();
auto localRef = this->repo.clearReference(*fuzzyRef,
{
.fallbackToRemote = false // NOLINT
});
// set version back
fuzzyRef->version = originalVersion;
api::types::v1::PackageManager1RequestInteractionAdditionalMessage additionalMessage;
if (localRef) {
additionalMessage.localRef = localRef->toString();
}
auto refRet = [&paras, &fuzzyRef, &curModule, this] {
if (!paras->repo) {
return this->repo.getRemoteReferenceByPriority(*fuzzyRef,
{ .onlyClearHighestPriority = false },
curModule);
auto installer = [action](PackageTask &task) {
LINGLONG_TRACE("ref installation task")
LogD("ref installation task is running");
auto res = action->doAction(task);
if (!res) {
LogE("ref installation failed: {}", res.error());
task.reportError(LINGLONG_ERRV(std::move(res).error().message(),
utils::error::ErrorCode::AppInstallFailed));
}
auto originalPriority = this->repo.promotePriority(paras->repo.value());
auto recover = linglong::utils::finally::finally([&] {
this->repo.recoverPriority(paras->repo.value(), originalPriority);
});
return this->repo.getRemoteReferenceByPriority(*fuzzyRef,
{ .onlyClearHighestPriority = true },
curModule);
}();
if (!refRet) {
if (refRet.error().code()
== static_cast<int>(utils::error::ErrorCode::AppNotFoundFromRemote)) {
return toDBusReply(utils::error::ErrorCode::AppInstallNotFoundFromRemote,
refRet.error().message());
}
return toDBusReply(utils::error::ErrorCode::AppInstallFailed, refRet.error().message());
}
auto remoteRef = refRet->reference;
additionalMessage.remoteRef = remoteRef.toString();
// 如果远程版本大于本地版本就升级,否则需要加--force降级如果本地没有则直接安装如果本地版本和远程版本相等就提示已安装
auto msgType = api::types::v1::InteractionMessageType::Install;
if (!additionalMessage.localRef.empty()) {
if (remoteRef.version == localRef->version) {
return toDBusReply(utils::error::ErrorCode::AppInstallAlreadyInstalled,
localRef->toString() + " is already installed");
}
if (remoteRef.version > localRef->version) {
msgType = api::types::v1::InteractionMessageType::Upgrade;
} else if (!paras->options.force) {
auto err = fmt::format("The latest version has been installed. If you want to "
"replace it, try using 'll-cli install {}/{} --force'",
remoteRef.id,
remoteRef.version.toString());
return toDBusReply(utils::error::ErrorCode::AppInstallNeedDowngrade, err);
}
}
auto refSpec = fmt::format("{}:{}/{}/{}/{}",
refRet->repo.name,
remoteRef.channel,
remoteRef.id,
remoteRef.arch.toStdString(),
curModule);
// Note: do not capture any reference of variable which defined in this func.
// it will be a dangling reference.
auto installer = [this,
remoteRef,
localRef = localRef.has_value()
? std::make_optional(std::move(localRef).value())
: std::nullopt,
curModule,
modules,
skipInteraction = paras->options.skipInteraction,
msgType,
additionalMessage,
originalRepo = refRet->repo](PackageTask &taskRef) {
// 升级需要用户交互
if (msgType == api::types::v1::InteractionMessageType::Upgrade && !skipInteraction) {
Q_EMIT RequestInteraction(QDBusObjectPath(taskRef.taskObjectPath()),
static_cast<int>(msgType),
utils::serialize::toQVariantMap(additionalMessage));
QEventLoop loop;
api::types::v1::InteractionReply interactionReply;
// Note: if capture the &taskRef into this lambda, be careful with it's life cycle.
connect(this,
&PackageManager::ReplyReceived,
[&interactionReply, &loop](const QVariantMap &reply) {
interactionReply =
*utils::serialize::fromQVariantMap<api::types::v1::InteractionReply>(
reply);
loop.exit(0);
});
loop.exec();
if (interactionReply.action != "yes") {
taskRef.updateState(linglong::api::types::v1::State::Canceled, "canceled");
}
}
if (isTaskDone(taskRef.subState())) {
return;
}
this->Install(taskRef,
remoteRef,
localRef,
localRef.has_value() ? this->repo.getModuleList(*localRef) : modules,
originalRepo);
};
auto taskRet = tasks.addNewTask({ refSpec }, std::move(installer), connection());
auto taskRet = tasks.addNewTask({ action->getTaskName() }, std::move(installer), connection());
if (!taskRet) {
return toDBusReply(utils::error::ErrorCode::Unknown, taskRet.error().message());
}
@ -1059,119 +909,10 @@ auto PackageManager::Install(const QVariantMap &parameters) noexcept -> QVariant
return utils::serialize::toQVariantMap(api::types::v1::PackageManager1PackageTaskResult{
.taskObjectPath = taskRef.taskObjectPath().toStdString(),
.code = 0,
.message = remoteRef.toString() + " is now installing",
.message = action->getTaskName() + " is now installing",
});
}
void PackageManager::Install(PackageTask &taskContext,
const package::Reference &newRef,
std::optional<package::Reference> oldRef,
const std::vector<std::string> &modules,
const std::optional<api::types::v1::Repo> &repo) noexcept
{
LINGLONG_TRACE("install app")
taskContext.updateState(linglong::api::types::v1::State::Processing,
"Installing " + newRef.toString());
utils::Transaction transaction;
// 仅安装远程存在的modules
auto installModules = [&repo, &newRef, &modules, this] {
if (!repo) {
return this->repo.getRemoteModuleListByPriority(newRef, modules, repo.has_value());
}
auto originalPriority = this->repo.promotePriority(repo->alias.value_or(repo->name));
auto recover = linglong::utils::finally::finally([&] {
this->repo.recoverPriority(repo->alias.value_or(repo->name), originalPriority);
});
return this->repo.getRemoteModuleListByPriority(newRef, modules, repo.has_value());
}();
if (!installModules) {
taskContext.reportError(LINGLONG_ERRV(std::move(installModules).error().message(),
utils::error::ErrorCode::AppInstallFailed));
return;
}
if (installModules->second.empty()) {
auto list = std::accumulate(modules.begin(), modules.end(), std::string(","));
taskContext.reportError(
LINGLONG_ERRV("These modules do not exist remotely: " + QString::fromStdString(list),
utils::error::ErrorCode::AppInstallModuleNotFound));
return;
}
InstallRef(taskContext,
newRef,
installModules->second,
installModules->first); // install modules
if (isTaskDone(taskContext.subState())) {
return;
}
transaction.addRollBack([this, &newRef, installModules = *installModules]() noexcept {
auto tmp = PackageTask::createTemporaryTask();
UninstallRef(tmp, newRef, installModules.second);
if (tmp.state() != linglong::api::types::v1::State::Succeed) {
LogE("failed to rollback install {}", newRef.toString());
}
});
taskContext.updateSubState(linglong::api::types::v1::SubState::PostAction,
"processing after install");
auto mergeRet = this->repo.mergeModules();
if (!mergeRet) {
qCritical() << "merge modules failed: " << mergeRet.error().message();
}
auto layer = this->repo.getLayerItem(newRef);
if (!layer) {
taskContext.reportError(LINGLONG_ERRV(std::move(layer).error().message(),
utils::error::ErrorCode::AppInstallFailed));
return;
}
// only app should do 'remove' and 'export'
if (layer->info.kind == "app") {
// remove all previous modules
if (oldRef) {
auto ret = this->removeAfterInstall(*oldRef, newRef, modules);
if (!ret) {
auto msg = fmt::format("Failed to remove old reference {} after install {}: {}",
oldRef->toString(),
newRef.toString(),
ret.error().message());
taskContext.reportError(LINGLONG_ERRV(QString::fromStdString(msg),
utils::error::ErrorCode::AppInstallFailed));
return;
}
} else {
this->repo.exportReference(newRef);
}
auto result = this->tryGenerateCache(newRef);
if (!result) {
taskContext.reportError(
LINGLONG_ERRV("Failed to generate some cache.\n" + result.error().message(),
utils::error::ErrorCode::AppInstallFailed));
return;
}
}
auto ret = executePostInstallHooks(newRef);
if (!ret) {
taskContext.updateState(linglong::api::types::v1::State::Failed,
"Failed to execute postInstall hooks.\n" + ret.error().message());
return;
}
transaction.commit();
taskContext.updateState(linglong::api::types::v1::State::Succeed,
"Install " + newRef.toString() + " (from repo: "
+ installModules->first.name.c_str() + ") " + " success");
}
void PackageManager::InstallRef(PackageTask &taskContext,
const package::Reference &ref,
std::vector<std::string> modules,

View File

@ -0,0 +1,275 @@
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "ref_installation.h"
#include "linglong/package_manager/package_manager.h"
#include "linglong/repo/ostree_repo.h"
#include "linglong/repo/repo_cache.h"
#include "linglong/utils/log/log.h"
namespace linglong::service {
std::shared_ptr<RefInstallationAction>
RefInstallationAction::create(package::FuzzyReference fuzzyRef,
std::vector<std::string> modules,
PackageManager &pm,
repo::OSTreeRepo &repo,
api::types::v1::CommonOptions opts,
std::optional<api::types::v1::Repo> usedRepo)
{
auto p =
new RefInstallationAction(fuzzyRef, modules, pm, repo, std::move(opts), std::move(usedRepo));
return std::shared_ptr<RefInstallationAction>(p);
}
void RefInstallationAction::checkModules(const std::vector<std::string> &modules,
bool &hasBinary,
bool &extraModule)
{
for (const auto &module : modules) {
if (module == "binary" || module == "runtime") {
hasBinary = true;
} else {
extraModule = true;
}
}
}
bool RefInstallationAction::extraModuleOnly(const std::vector<std::string> &modules)
{
bool hasBinary = false;
bool extraModule = false;
checkModules(modules, hasBinary, extraModule);
return extraModule && !hasBinary;
}
RefInstallationAction::RefInstallationAction(package::FuzzyReference fuzzyRef,
std::vector<std::string> modules,
PackageManager &pm,
repo::OSTreeRepo &repo,
api::types::v1::CommonOptions opts,
std::optional<api::types::v1::Repo> usedRepo)
: Action(pm, repo, opts)
, fuzzyRef(std::move(fuzzyRef))
, modules(std::move(modules))
, usedRepo(std::move(usedRepo))
{
}
utils::error::Result<void> RefInstallationAction::prepare()
{
LINGLONG_TRACE("ref installation prepare");
if (prepared) {
return LINGLONG_OK;
}
auto extraOnly = extraModuleOnly(modules);
auto localRef = repo.latestLocalReference(fuzzyRef);
if (extraOnly) {
if (!localRef) {
return LINGLONG_ERR("no matched binary module found");
}
// Use the locally installed binary module's version to locate corresponding remote
// modules
fuzzyRef.version = localRef->version.toString();
// filter out the modules that are already installed
std::vector<std::string> toInstall;
for (const auto &module : modules) {
auto ret = repo.getLayerItem(*localRef, module);
if (!ret) {
toInstall.emplace_back(module);
}
}
modules = std::move(toInstall);
} else if (localRef && localRef->version.toString() == fuzzyRef.version) {
// if the user-specified version exactly matches the locally installed version
return LINGLONG_ERR("package already installed",
utils::error::ErrorCode::AppInstallAlreadyInstalled);
}
this->taskName = fmt::format("Installing {}", fuzzyRef.toString());
this->extraOnly = extraOnly;
prepared = true;
return LINGLONG_OK;
}
utils::error::Result<void> RefInstallationAction::doAction(PackageTask &task)
{
LINGLONG_TRACE("ref installation do action");
if (!prepared) {
return LINGLONG_ERR("action not prepared");
}
auto res = preInstall(task);
if (!res) {
return res;
}
res = install(task);
if (!res) {
return res;
}
return postInstall(task);
}
utils::error::Result<void> RefInstallationAction::preInstall(PackageTask &task)
{
LINGLONG_TRACE("ref installation preInstall");
// find all candidate remote packages
auto candidates = repo.matchRemoteByPriority(fuzzyRef, usedRepo);
if (!candidates) {
return LINGLONG_ERR(candidates);
}
// find the latest package from candidates
auto target = candidates->getLatestPackage();
if (!target) {
return LINGLONG_ERR("package not found",
utils::error::ErrorCode::AppInstallNotFoundFromRemote);
}
auto operation = getActionOperation(target->second.get(), extraOnly);
if (!operation) {
return LINGLONG_ERR(operation);
}
if (operation->newRef) {
operation->newRef->repo = target->first.get();
}
if (operation->operation == ActionOperation::Overwrite) {
return LINGLONG_ERR("package already installed",
utils::error::ErrorCode::AppInstallAlreadyInstalled);
}
if (operation->operation == ActionOperation::Downgrade && !options.force) {
return LINGLONG_ERR("latest version already installed",
utils::error::ErrorCode::AppInstallNeedDowngrade);
}
if (operation->operation == ActionOperation::Policy::Upgrade && !options.skipInteraction) {
auto additionalMessage = api::types::v1::PackageManager1RequestInteractionAdditionalMessage{
.localRef = operation->oldRef->toString(),
.remoteRef = operation->newRef->reference.toString()
};
if (!pm.waitConfirm(task,
api::types::v1::InteractionMessageType::Upgrade,
additionalMessage)) {
return LINGLONG_ERR("action canceled");
}
}
this->operation = std::move(operation).value();
this->candidates = std::move(candidates).value();
return LINGLONG_OK;
}
utils::error::Result<void> RefInstallationAction::install(PackageTask &task)
{
LINGLONG_TRACE("ref installation install");
const auto &newRef = operation.newRef->reference;
task.updateState(linglong::api::types::v1::State::Processing,
fmt::format("Installing {}", newRef.toString()));
auto remoteModules = candidates.getReferenceModules(newRef);
if (remoteModules.empty()) {
return LINGLONG_ERR("no modules found");
}
auto installModules = std::vector<std::string>{};
for (const auto &module : modules) {
if (std::find(remoteModules.begin(), remoteModules.end(), module) != remoteModules.end()) {
installModules.emplace_back(module);
}
}
if (installModules.empty()) {
return LINGLONG_ERR("no modules found");
}
pm.InstallRef(task, newRef, installModules, operation.newRef->repo);
if (task.isTaskDone()) {
return LINGLONG_ERR("install canceled");
}
transaction.addRollBack([this, &installModules, &newRef]() noexcept {
auto tmp = PackageTask::createTemporaryTask();
pm.UninstallRef(tmp, newRef, installModules);
if (tmp.state() != linglong::api::types::v1::State::Succeed) {
LogE("failed to rollback install {}", newRef.toString());
}
});
return LINGLONG_OK;
}
utils::error::Result<void> RefInstallationAction::postInstall(PackageTask &task)
{
LINGLONG_TRACE("ref installation postInstall");
task.updateSubState(linglong::api::types::v1::SubState::PostAction, "processing after install");
auto mergeRet = this->repo.mergeModules();
if (!mergeRet) {
LogE("failed to merge modules: {}", mergeRet.error());
}
const auto &newRef = operation.newRef->reference;
const auto &oldRef = operation.oldRef;
auto layer = this->repo.getLayerItem(newRef);
if (!layer) {
task.reportError(
LINGLONG_ERRV(layer.error().message(), utils::error::ErrorCode::AppInstallFailed));
return LINGLONG_ERR("failed to get layer item", layer);
}
// only app should do 'remove' and 'export'
if (layer->info.kind == "app") {
// remove all previous modules
if (oldRef) {
auto ret = pm.removeAfterInstall(*oldRef, newRef, modules);
if (!ret) {
auto msg = fmt::format("Failed to remove old reference {} after install {}: {}",
oldRef->toString(),
newRef.toString(),
ret.error().message());
task.reportError(LINGLONG_ERRV(msg, utils::error::ErrorCode::AppInstallFailed));
return LINGLONG_ERR(ret);
}
} else {
this->repo.exportReference(newRef);
}
auto result = pm.tryGenerateCache(newRef);
if (!result) {
task.reportError(
LINGLONG_ERRV("Failed to generate some cache.\n" + result.error().message(),
utils::error::ErrorCode::AppInstallFailed));
return LINGLONG_ERR(result);
}
}
auto ret = pm.executePostInstallHooks(newRef);
if (!ret) {
task.updateState(linglong::api::types::v1::State::Failed,
"Failed to execute postInstall hooks.\n" + ret.error().message());
return LINGLONG_ERR(ret);
}
transaction.commit();
task.updateState(linglong::api::types::v1::State::Succeed,
fmt::format("Install {} (from repo: {}) success",
newRef.toString(),
operation.newRef->repo.name));
return LINGLONG_OK;
}
} // namespace linglong::service

View File

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once
#include "linglong/package/fuzzy_reference.h"
#include "linglong/package_manager/action.h"
#include "linglong/repo/remote_packages.h"
#include "linglong/utils/transaction.h"
namespace linglong::service {
class RefInstallationAction : public Action
{
public:
static std::shared_ptr<RefInstallationAction>
create(package::FuzzyReference fuzzyRef,
std::vector<std::string> modules,
PackageManager &pm,
repo::OSTreeRepo &repo,
api::types::v1::CommonOptions options,
std::optional<api::types::v1::Repo> usedRepo);
static void checkModules(const std::vector<std::string> &modules,
bool &hasBinary,
bool &extraModule);
static bool extraModuleOnly(const std::vector<std::string> &modules);
virtual ~RefInstallationAction() = default;
virtual utils::error::Result<void> prepare() override;
virtual utils::error::Result<void> doAction(PackageTask &task) override;
virtual std::string getTaskName() const override { return taskName; }
protected:
virtual utils::error::Result<void> preInstall(PackageTask &task);
virtual utils::error::Result<void> install(PackageTask &task);
virtual utils::error::Result<void> postInstall(PackageTask &task);
private:
RefInstallationAction(package::FuzzyReference fuzzyRef,
std::vector<std::string> modules,
PackageManager &pm,
repo::OSTreeRepo &repo,
api::types::v1::CommonOptions options,
std::optional<api::types::v1::Repo> usedRepo);
package::FuzzyReference fuzzyRef;
std::vector<std::string> modules;
bool prepared = false;
bool extraOnly = false;
ActionOperation operation;
std::string taskName;
utils::Transaction transaction;
std::optional<api::types::v1::Repo> usedRepo;
repo::RemotePackages candidates;
};
} // namespace linglong::service

View File

@ -125,8 +125,6 @@ utils::error::Result<void> UabInstallationAction::checkUABLayersConstrain(
}
const auto &front = layers.front().info;
bool hasBinary = false;
bool extraModule = false;
for (const auto &layer : layers) {
auto arch = package::Architecture::parse(layer.info.arch[0]);
if (!arch) {
@ -144,21 +142,15 @@ utils::error::Result<void> UabInstallationAction::checkUABLayersConstrain(
if (layer.info.version != front.version) {
return LINGLONG_ERR("modules have different version");
}
}
const auto &module = layer.info.packageInfoV2Module;
if (module == "binary" || module == "runtime") {
hasBinary = true;
} else {
extraModule = true;
if (extraModuleOnly(layers)) {
auto fuzzyRef =
package::FuzzyReference::create(front.channel, front.id, front.version, std::nullopt);
if (!fuzzyRef) {
return LINGLONG_ERR(fuzzyRef);
}
}
auto fuzzyRef =
package::FuzzyReference::create(front.channel, front.id, front.version, std::nullopt);
if (!fuzzyRef) {
return LINGLONG_ERR(fuzzyRef);
}
if (extraModule && !hasBinary) {
auto localRef = repo.clearReference(*fuzzyRef,
{
.forceRemote = false,
@ -178,70 +170,23 @@ utils::error::Result<void> UabInstallationAction::checkUABLayersConstrain(
return LINGLONG_OK;
}
utils::error::Result<TaskAction>
UabInstallationAction::getTaskAction(repo::OSTreeRepo &repo, const CheckedLayers &checkedLayers)
bool UabInstallationAction::extraModuleOnly(const std::vector<api::types::v1::UabLayer> &layers)
{
LINGLONG_TRACE("get task action");
const api::types::v1::UabLayer &toCheck =
checkedLayers.first.empty() ? checkedLayers.second.front() : checkedLayers.first.front();
auto fuzzyRef = package::FuzzyReference::create(toCheck.info.channel,
toCheck.info.id,
std::nullopt,
std::nullopt);
if (!fuzzyRef) {
return LINGLONG_ERR(fuzzyRef);
}
auto checkRef = package::Reference::fromPackageInfo(toCheck.info);
if (!checkRef) {
return LINGLONG_ERR(checkRef);
}
const auto &kind = toCheck.info.kind;
TaskAction action;
action.additionalMessage.remoteRef = checkRef->toString();
auto installedRef = repo.latestLocalReference(*fuzzyRef);
if (installedRef) {
action.additionalMessage.localRef = installedRef->toString();
if (checkRef->version == installedRef->version) {
action.policy = TaskAction::Policy::Overwrite;
} else if (checkRef->version > installedRef->version) {
if (kind == "app") {
action.policy = TaskAction::Policy::Upgrade;
action.msgType = api::types::v1::InteractionMessageType::Upgrade;
} else {
action.policy = TaskAction::Policy::Install;
}
} else {
if (kind == "app") {
action.policy = TaskAction::Policy::Downgrade;
} else {
action.policy = TaskAction::Policy::Install;
}
for (const auto &layer : layers) {
const auto &module = layer.info.packageInfoV2Module;
if (module == "binary" || module == "runtime") {
return false;
}
} else {
action.policy = TaskAction::Policy::Install;
}
action.kind = kind;
action.newRef = std::move(checkRef).value();
if (installedRef) {
action.oldRef = std::move(installedRef).value();
}
return action;
return true;
}
UabInstallationAction::UabInstallationAction(int uabFD,
PackageManager &pm,
repo::OSTreeRepo &repo,
api::types::v1::CommonOptions opts)
: fd(dup(uabFD))
, pm(pm)
, repo(repo)
, options(std::move(opts))
: Action(pm, repo, opts)
, fd(dup(uabFD))
{
}
@ -292,22 +237,6 @@ utils::error::Result<void> UabInstallationAction::prepare()
checkedLayers = std::move(res).value();
}
auto action = getTaskAction(repo, checkedLayers);
if (!action) {
return LINGLONG_ERR(action);
}
if (action->policy == TaskAction::Policy::Overwrite) {
return LINGLONG_ERR("package already installed",
utils::error::ErrorCode::AppInstallAlreadyInstalled);
}
if (action->policy == TaskAction::Policy::Downgrade && !options.force) {
return LINGLONG_ERR("latest version already installed",
utils::error::ErrorCode::AppInstallNeedDowngrade);
}
this->action = std::move(action).value();
this->taskName = fmt::format("Installing {}", uabFile->symLinkTarget());
this->uabFile = std::move(uabFile);
@ -342,12 +271,36 @@ utils::error::Result<void> UabInstallationAction::preInstall(PackageTask &task)
task.updateState(linglong::api::types::v1::State::Processing, "installing uab");
task.updateSubState(linglong::api::types::v1::SubState::PreAction, "prepare environment");
if (action.policy == TaskAction::Policy::Upgrade && !options.skipInteraction) {
if (!pm.waitConfirm(task, action.msgType, action.additionalMessage)) {
const auto &toCheck = checkedLayers.first.empty() ? checkedLayers.second : checkedLayers.first;
auto operation = getActionOperation(toCheck.front().info, extraModuleOnly(toCheck));
if (!operation) {
return LINGLONG_ERR(operation);
}
if (operation->operation == ActionOperation::Overwrite) {
return LINGLONG_ERR("package already installed",
utils::error::ErrorCode::AppInstallAlreadyInstalled);
}
if (operation->operation == ActionOperation::Downgrade && !options.force) {
return LINGLONG_ERR("latest version already installed",
utils::error::ErrorCode::AppInstallNeedDowngrade);
}
if (operation->operation == ActionOperation::Upgrade && !options.skipInteraction) {
auto additionalMessage = api::types::v1::PackageManager1RequestInteractionAdditionalMessage{
.localRef = operation->oldRef->toString(),
.remoteRef = operation->newRef->reference.toString()
};
if (!pm.waitConfirm(task,
api::types::v1::InteractionMessageType::Upgrade,
additionalMessage)) {
return LINGLONG_ERR("action canceled");
}
}
this->operation = std::move(operation).value();
return LINGLONG_OK;
}
@ -373,8 +326,8 @@ utils::error::Result<void> UabInstallationAction::postInstall(PackageTask &task)
{
LINGLONG_TRACE("uab installation postInstall");
const auto &newRef = action.newRef;
const auto &oldRef = action.oldRef;
const auto &newRef = operation.newRef->reference;
const auto &oldRef = operation.oldRef;
if (!oldRef) {
auto mergeRet = repo.mergeModules();
@ -387,17 +340,17 @@ utils::error::Result<void> UabInstallationAction::postInstall(PackageTask &task)
// 1. replace installed version
// 2. export entries
// 3. generate cache
if (action.kind == "app") {
if (operation.kind == "app") {
if (oldRef) {
auto ret = pm.removeAfterInstall(*oldRef, *newRef, repo.getModuleList(*oldRef));
auto ret = pm.removeAfterInstall(*oldRef, newRef, repo.getModuleList(*oldRef));
if (!ret) {
LogE("remove old reference after install newer version failed: {}", ret.error());
return LINGLONG_ERR(ret);
}
} else {
// export directly
this->repo.exportReference(*newRef);
auto result = pm.tryGenerateCache(*newRef);
this->repo.exportReference(newRef);
auto result = pm.tryGenerateCache(newRef);
if (!result) {
auto msg =
fmt::format("Failed to generate some cache: {}", result.error().message());
@ -407,7 +360,7 @@ utils::error::Result<void> UabInstallationAction::postInstall(PackageTask &task)
}
}
auto ret = pm.executePostInstallHooks(*newRef);
auto ret = pm.executePostInstallHooks(newRef);
if (!ret) {
task.reportError(std::move(ret).error());
return LINGLONG_ERR("failed to execute post install hooks");

View File

@ -8,6 +8,7 @@
#include "linglong/api/types/v1/PackageManager1RequestInteractionAdditionalMessage.hpp"
#include "linglong/api/types/v1/UabLayer.hpp"
#include "linglong/package/uab_file.h"
#include "linglong/package_manager/action.h"
#include "linglong/package_manager/package_manager.h"
#include "linglong/package_manager/package_task.h"
#include "linglong/repo/ostree_repo.h"
@ -18,25 +19,7 @@
namespace linglong::service {
struct TaskAction
{
enum Policy {
Upgrade,
Install,
Remove,
Overwrite,
Downgrade,
} policy;
api::types::v1::InteractionMessageType msgType;
api::types::v1::PackageManager1RequestInteractionAdditionalMessage additionalMessage;
std::string kind;
std::optional<package::Reference> oldRef;
std::optional<package::Reference> newRef;
};
class UabInstallationAction
class UabInstallationAction : public Action
{
public:
static std::shared_ptr<UabInstallationAction> create(int uabFD,
@ -52,8 +35,7 @@ public:
repo::OSTreeRepo &repo, const std::vector<linglong::api::types::v1::UabLayer> &layers);
static utils::error::Result<void> checkUABLayersConstrain(
repo::OSTreeRepo &repo, const std::vector<api::types::v1::UabLayer> &layers);
static utils::error::Result<TaskAction> getTaskAction(repo::OSTreeRepo &repo,
const CheckedLayers &checkedLayers);
static bool extraModuleOnly(const std::vector<api::types::v1::UabLayer> &layers);
virtual ~UabInstallationAction();
@ -79,13 +61,10 @@ private:
std::optional<std::string> subRef = std::nullopt);
int fd;
PackageManager &pm;
repo::OSTreeRepo &repo;
api::types::v1::CommonOptions options;
TaskAction action;
ActionOperation operation;
std::string taskName;
CheckedLayers checkedLayers;
bool extraOnly = false;
std::unique_ptr<package::UABFile> uabFile;
utils::Transaction transaction;
std::filesystem::path uabMountPoint;

View File

@ -1755,49 +1755,33 @@ OSTreeRepo::searchRemote(const package::FuzzyReference &fuzzyRef,
{
LINGLONG_TRACE("list remote packages");
auto client = this->createClientV2(repo.url);
request_fuzzy_search_req_t req{ nullptr, nullptr, nullptr, nullptr, nullptr };
auto freeIfNotNull = utils::finally::finally([&req] {
if (req.app_id != nullptr) {
free(req.app_id); // NOLINT
}
if (req.channel != nullptr) {
free(req.channel); // NOLINT
}
if (req.version != nullptr) {
free(req.version); // NOLINT
}
if (req.arch != nullptr) {
free(req.arch); // NOLINT
}
if (req.repo_name != nullptr) {
free(req.repo_name); // NOLINT
}
});
LogD("searchRemote use repo {}", nlohmann::json(repo).dump());
req.app_id = ::strndup(fuzzyRef.id.data(), fuzzyRef.id.size());
if (req.app_id == nullptr) {
auto client = this->createClientV2(repo.url);
char *app_id = strndup(fuzzyRef.id.data(), fuzzyRef.id.size());
if (app_id == nullptr) {
return LINGLONG_ERR(fmt::format("strndup app_id failed: {}", fuzzyRef.id));
}
req.repo_name = ::strndup(repo.name.data(), repo.name.size());
if (req.repo_name == nullptr) {
char *repo_name = strndup(repo.name.data(), repo.name.size());
if (repo_name == nullptr) {
return LINGLONG_ERR(fmt::format("strndup repo_name failed: {}", repo.name));
}
char *channel = nullptr;
if (fuzzyRef.channel) {
auto channel = fuzzyRef.channel.value();
req.channel = strndup(channel.data(), channel.size());
if (req.channel == nullptr) {
return LINGLONG_ERR(QString{ "strndup channel failed: %1" }.arg(channel.data()));
channel = strndup(fuzzyRef.channel->data(), fuzzyRef.channel->size());
if (channel == nullptr) {
return LINGLONG_ERR(fmt::format("strndup channel failed: {}", *fuzzyRef.channel));
}
}
// use prefix matching on version strings when searching the remote server
char *version = nullptr;
if (fuzzyRef.version) {
auto version = fuzzyRef.version.value();
req.version = strndup(version.data(), version.size());
if (req.version == nullptr) {
return LINGLONG_ERR(QString{ "strndup version failed: %1" }.arg(version.data()));
version = strndup(fuzzyRef.version->data(), fuzzyRef.version->size());
if (version == nullptr) {
return LINGLONG_ERR(fmt::format("strndup version failed: {}", *fuzzyRef.version));
}
}
@ -1806,14 +1790,20 @@ OSTreeRepo::searchRemote(const package::FuzzyReference &fuzzyRef,
return LINGLONG_ERR(defaultArch);
}
auto arch = fuzzyRef.arch.value_or(*defaultArch);
auto archStr = arch.toStdString();
req.arch = strndup(archStr.data(), archStr.size());
if (req.arch == nullptr) {
return LINGLONG_ERR(QString{ "strndup arch failed: %1" }.arg(archStr.data()));
auto arch = fuzzyRef.arch.value_or(*defaultArch).toStdString();
char *archStr = strndup(arch.data(), arch.size());
if (archStr == nullptr) {
return LINGLONG_ERR(fmt::format("strndup arch failed: {}", arch));
}
auto response = client->fuzzySearch(&req);
auto req = request_fuzzy_search_req_create(app_id, archStr, channel, repo_name, version);
if (!req) {
return LINGLONG_ERR("failed to create request");
}
auto freeIfNotNull = utils::finally::finally([req] {
request_fuzzy_search_req_free(req);
});
auto response = client->fuzzySearch(req);
if (!response) {
return LINGLONG_ERR("failed to send request to remote server\nIf the network is slow, "
"set a longer timeout via the LINGLONG_CONNECT_TIMEOUT environment "
@ -1834,11 +1824,7 @@ OSTreeRepo::searchRemote(const package::FuzzyReference &fuzzyRef,
: utils::error::ErrorCode::NetworkError));
}
if (response->data == nullptr) {
return {};
}
if (response->data->count == 0) {
if (response->data == nullptr || response->data->count == 0) {
return {};
}
@ -1881,6 +1867,49 @@ OSTreeRepo::searchRemote(const package::FuzzyReference &fuzzyRef,
return std::move(pkgInfos);
}
utils::error::Result<repo::RemotePackages>
OSTreeRepo::matchRemoteByPriority(const package::FuzzyReference &fuzzyRef,
const std::optional<api::types::v1::Repo> &repo) const noexcept
{
repo::RemotePackages remotePackages;
if (repo) {
auto list = this->searchRemote(fuzzyRef, *repo, true);
if (!list) {
LogW("failed to list remote packages from {}: {}", repo->name, list.error());
return remotePackages;
}
if (!list->empty()) {
remotePackages.addPackages(*repo, std::move(list).value());
}
} else {
auto repos = this->getPriorityGroupedRepos();
for (const auto &repoGroup : repos) {
for (const auto &repo : repoGroup) {
auto list = this->searchRemote(fuzzyRef, repo, true);
if (!list) {
LogW("failed to list remote packages from {}: {}", repo.name, list.error());
continue;
}
if (list->empty()) {
continue;
}
remotePackages.addPackages(repo, std::move(list).value());
}
// try a lower-priority repo when no matched result
if (!remotePackages.empty()) {
break;
}
}
}
return remotePackages;
}
void OSTreeRepo::unexportReference(const std::string &layerDir) noexcept
{
QString layerDirStr = layerDir.c_str();

View File

@ -15,6 +15,7 @@
#include "linglong/package_manager/package_task.h"
#include "linglong/repo/client_factory.h"
#include "linglong/repo/config.h"
#include "linglong/repo/remote_packages.h"
#include "linglong/repo/repo_cache.h"
#include "linglong/utils/error/error.h"
@ -60,7 +61,7 @@ public:
[[nodiscard]] utils::error::Result<api::types::v1::Repo>
getRepoByAlias(const std::string &alias) const noexcept;
[[nodiscard]] std::vector<api::types::v1::Repo> getHighestPriorityRepos() const noexcept;
[[nodiscard]] std::vector<std::vector<api::types::v1::Repo>>
[[nodiscard]] virtual std::vector<std::vector<api::types::v1::Repo>>
getPriorityGroupedRepos() const noexcept;
repoPriority_t promotePriority(const std::string &alias) noexcept;
void recoverPriority(const std::string &alias, const repoPriority_t &priority) noexcept;
@ -107,10 +108,14 @@ public:
const package::FuzzyReference &fuzzyRef,
const api::types::v1::Repo &repo,
bool semanticMatching = false) const noexcept;
utils::error::Result<repo::RemotePackages> virtual matchRemoteByPriority(
const package::FuzzyReference &fuzzyRef,
const std::optional<api::types::v1::Repo> &repo = std::nullopt) const noexcept;
utils::error::Result<std::vector<api::types::v1::RepositoryCacheLayersItem>>
listLayerItem() const noexcept;
[[nodiscard]] utils::error::Result<std::vector<api::types::v1::RepositoryCacheLayersItem>>
[[nodiscard]] virtual utils::error::Result<
std::vector<api::types::v1::RepositoryCacheLayersItem>>
listLocalBy(const linglong::repo::repoCacheQuery &query) const noexcept;
utils::error::Result<int64_t>
getLayerCreateTime(const api::types::v1::RepositoryCacheLayersItem &item) const noexcept;

View File

@ -0,0 +1,64 @@
/*
* SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#include "remote_packages.h"
namespace linglong::repo {
RemotePackages &RemotePackages::addPackages(Repo repo, std::vector<PackageInfoV2> packages)
{
repoPackages.emplace_back(std::make_pair(std::move(repo), std::move(packages)));
return *this;
}
const utils::error::Result<PackageWithRepo> RemotePackages::getLatestPackage() const
{
LINGLONG_TRACE("get latest package");
if (empty()) {
return LINGLONG_ERR("packages is empty");
}
auto compare = [](const auto &a, const auto &b) -> bool {
auto versionA = package::Version::parse(a.version);
auto versionB = package::Version::parse(b.version);
if (!versionB) {
return false;
} else if (!versionA) {
return true;
}
return *versionA < *versionB;
};
std::optional<PackageWithRepo> latest;
for (const auto &packages : repoPackages) {
auto max = std::max_element(packages.second.begin(), packages.second.end(), compare);
if (max != packages.second.end() && (!latest || compare(latest->second.get(), *max))) {
latest = std::make_pair(std::ref(packages.first), std::ref(*max));
}
}
return *latest;
}
std::vector<std::string> RemotePackages::getReferenceModules(const package::Reference &ref) const
{
std::vector<std::string> modules;
for (const auto &packages : repoPackages) {
for (const auto &package : packages.second) {
if (package.id == ref.id && package.channel == ref.channel
&& package.version == ref.version.toString()
&& package.arch[0] == ref.arch.toStdString()) {
modules.emplace_back(package.packageInfoV2Module);
}
}
}
return modules;
}
} // namespace linglong::repo

View File

@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#pragma once
#include "linglong/api/types/v1/PackageInfoV2.hpp"
#include "linglong/api/types/v1/Repo.hpp"
#include "linglong/package/reference.h"
#include "linglong/utils/error/error.h"
#include <list>
namespace linglong::repo {
using linglong::api::types::v1::PackageInfoV2;
using linglong::api::types::v1::Repo;
using PackagesWithRepo = std::pair<Repo, std::vector<PackageInfoV2>>;
using PackageWithRepo =
std::pair<std::reference_wrapper<const Repo>, std::reference_wrapper<const PackageInfoV2>>;
class RemotePackages
{
public:
RemotePackages() = default;
RemotePackages(RemotePackages &&) = default;
RemotePackages &operator=(RemotePackages &&) = default;
RemotePackages &addPackages(Repo repo, std::vector<PackageInfoV2> packages);
const std::list<PackagesWithRepo> &getRepoPackages() const { return repoPackages; }
bool empty() const { return repoPackages.empty(); }
const utils::error::Result<PackageWithRepo> getLatestPackage() const;
std::vector<std::string> getReferenceModules(const package::Reference &ref) const;
private:
std::list<PackagesWithRepo> repoPackages;
};
} // namespace linglong::repo

View File

@ -251,11 +251,8 @@ RepoCache::queryLayerItem(const repoCacheQuery &query) const noexcept
}
if (query.deleted) {
if (!layer.deleted) {
continue;
}
if (query.deleted.value() != layer.deleted.value()) {
auto layerDeleted = layer.deleted.value_or(false);
if (query.deleted.value() != layerDeleted) {
continue;
}
}

View File

@ -33,6 +33,7 @@ pfl_add_executable(
src/linglong/package/uab_file_test.cpp
src/linglong/package/version_test.cpp
src/linglong/package/versionv2_test.cpp
src/linglong/package_manager/action_test.cpp
src/linglong/package_manager/uab_installation_test.cpp
src/linglong/repo/client_factory_test.cpp
src/linglong/repo/config_test.cpp

View File

@ -0,0 +1,309 @@
/*
* SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*/
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "../../common/tempdir.h"
#include "linglong/package_manager/action.h"
#include "linglong/package_manager/package_manager.h"
#include "linglong/repo/ostree_repo.h"
#include "linglong/runtime/container_builder.h"
#include "ocppi/cli/crun/Crun.hpp"
namespace linglong::runtime {
class ContainerBuilder;
}
namespace linglong::service {
class PackageTask;
}
using namespace linglong;
using ::testing::_;
using ::testing::Return;
class MockRepo : public repo::OSTreeRepo
{
public:
MockRepo(const std::filesystem::path &path)
: repo::OSTreeRepo(
QDir(path.c_str()),
api::types::v1::RepoConfigV2{ .defaultRepo = "", .repos = {}, .version = 2 })
{
}
MOCK_METHOD(utils::error::Result<std::vector<api::types::v1::RepositoryCacheLayersItem>>,
listLocalBy,
(const repo::repoCacheQuery &query),
(override, const, noexcept));
};
class MockAction : public service::Action
{
public:
MockAction(service::PackageManager &pm,
repo::OSTreeRepo &repo,
api::types::v1::CommonOptions opts)
: service::Action(pm, repo, opts)
{
}
utils::error::Result<service::ActionOperation>
testActionOperation(const api::types::v1::PackageInfoV2 &target, bool extraModuleOnly)
{
return getActionOperation(target, extraModuleOnly);
}
utils::error::Result<void> prepare() override { return utils::error::Result<void>(); }
utils::error::Result<void> doAction([[maybe_unused]] service::PackageTask &task) override
{
return utils::error::Result<void>();
}
std::string getTaskName() const override { return "mock_action"; }
};
class ActionTest : public ::testing::Test
{
protected:
void SetUp() override
{
tempDir = std::make_unique<TempDir>();
repo = std::make_unique<MockRepo>(tempDir->path());
cli = ocppi::cli::crun::Crun::New(tempDir->path()).value();
containerBuilder = std::make_unique<runtime::ContainerBuilder>(*cli);
pm = std::make_unique<service::PackageManager>(*repo, *containerBuilder, nullptr);
mockAction = std::make_unique<MockAction>(*pm, *repo, api::types::v1::CommonOptions());
}
void TearDown() override
{
mockAction.reset();
pm.reset();
containerBuilder.reset();
cli.reset();
repo.reset();
tempDir.reset();
}
std::unique_ptr<TempDir> tempDir;
std::unique_ptr<ocppi::cli::crun::Crun> cli;
std::unique_ptr<runtime::ContainerBuilder> containerBuilder;
std::unique_ptr<MockRepo> repo;
std::unique_ptr<service::PackageManager> pm;
std::unique_ptr<MockAction> mockAction;
};
TEST_F(ActionTest, InstallNewApp)
{
api::types::v1::PackageInfoV2 packageInfo = {
.arch = std::vector<std::string>{ "x86_64" },
.channel = "main",
.id = "id1",
.kind = "app",
.packageInfoV2Module = "binary",
.version = "1.0.0",
};
EXPECT_CALL(*repo, listLocalBy(_))
.WillOnce(Return(std::vector<api::types::v1::RepositoryCacheLayersItem>{}));
auto result = mockAction->testActionOperation(packageInfo, false);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->operation, service::ActionOperation::Policy::Install);
EXPECT_EQ(result->kind, "app");
EXPECT_EQ(result->oldRef, std::nullopt);
EXPECT_EQ(result->newRef->reference.toString(), "main:id1/1.0.0/x86_64");
}
TEST_F(ActionTest, OverwriteApp)
{
api::types::v1::PackageInfoV2 packageInfo = {
.arch = std::vector<std::string>{ "x86_64" },
.channel = "main",
.id = "id1",
.kind = "app",
.packageInfoV2Module = "binary",
.version = "1.0.0",
};
EXPECT_CALL(*repo, listLocalBy(_))
.WillOnce(Return(std::vector<api::types::v1::RepositoryCacheLayersItem>{
api::types::v1::RepositoryCacheLayersItem{
.info = packageInfo,
} }));
auto result = mockAction->testActionOperation(packageInfo, false);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->operation, service::ActionOperation::Policy::Overwrite);
EXPECT_EQ(result->kind, "app");
EXPECT_EQ(result->oldRef->toString(), "main:id1/1.0.0/x86_64");
EXPECT_EQ(result->newRef->reference.toString(), "main:id1/1.0.0/x86_64");
}
TEST_F(ActionTest, UpgradeApp)
{
api::types::v1::PackageInfoV2 packageInfo = {
.arch = std::vector<std::string>{ "x86_64" },
.channel = "main",
.id = "id1",
.kind = "app",
.packageInfoV2Module = "binary",
.version = "2.0.0",
};
EXPECT_CALL(*repo, listLocalBy(_))
.WillOnce(Return(std::vector<api::types::v1::RepositoryCacheLayersItem>{
api::types::v1::RepositoryCacheLayersItem{ .info = {
.arch = std::vector<std::string>{ "x86_64" },
.channel = "main",
.id = "id1",
.kind = "app",
.packageInfoV2Module = "binary",
.version = "1.0.0",
} } }));
auto result = mockAction->testActionOperation(packageInfo, false);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->operation, service::ActionOperation::Policy::Upgrade);
EXPECT_EQ(result->kind, "app");
EXPECT_EQ(result->oldRef->toString(), "main:id1/1.0.0/x86_64");
EXPECT_EQ(result->newRef->reference.toString(), "main:id1/2.0.0/x86_64");
}
TEST_F(ActionTest, DowngradeApp)
{
api::types::v1::PackageInfoV2 packageInfo = {
.arch = std::vector<std::string>{ "x86_64" },
.channel = "main",
.id = "id1",
.kind = "app",
.packageInfoV2Module = "binary",
.version = "1.0.0",
};
EXPECT_CALL(*repo, listLocalBy(_))
.WillOnce(Return(std::vector<api::types::v1::RepositoryCacheLayersItem>{
api::types::v1::RepositoryCacheLayersItem{ .info = {
.arch = std::vector<std::string>{ "x86_64" },
.channel = "main",
.id = "id1",
.kind = "app",
.packageInfoV2Module = "binary",
.version = "2.0.0",
} } }));
auto result = mockAction->testActionOperation(packageInfo, false);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->operation, service::ActionOperation::Policy::Downgrade);
EXPECT_EQ(result->kind, "app");
EXPECT_EQ(result->oldRef->toString(), "main:id1/2.0.0/x86_64");
EXPECT_EQ(result->newRef->reference.toString(), "main:id1/1.0.0/x86_64");
}
TEST_F(ActionTest, InstallLowVersionRuntime)
{
api::types::v1::PackageInfoV2 packageInfo = {
.arch = std::vector<std::string>{ "x86_64" },
.channel = "main",
.id = "id1",
.kind = "runtime",
.packageInfoV2Module = "binary",
.version = "1.0.0",
};
EXPECT_CALL(*repo, listLocalBy(_))
.WillOnce(Return(std::vector<api::types::v1::RepositoryCacheLayersItem>{
api::types::v1::RepositoryCacheLayersItem{ .info = {
.arch = std::vector<std::string>{ "x86_64" },
.channel = "main",
.id = "id1",
.kind = "runtime",
.packageInfoV2Module = "binary",
.version = "2.0.0",
} } }));
auto result = mockAction->testActionOperation(packageInfo, false);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->operation, service::ActionOperation::Policy::Install);
EXPECT_EQ(result->kind, "runtime");
EXPECT_EQ(result->oldRef, std::nullopt);
EXPECT_EQ(result->newRef->reference.toString(), "main:id1/1.0.0/x86_64");
}
TEST_F(ActionTest, InstallHighVersionRuntime)
{
api::types::v1::PackageInfoV2 packageInfo = {
.arch = std::vector<std::string>{ "x86_64" },
.channel = "main",
.id = "id1",
.kind = "runtime",
.packageInfoV2Module = "binary",
.version = "2.0.0",
};
EXPECT_CALL(*repo, listLocalBy(_))
.WillOnce(Return(std::vector<api::types::v1::RepositoryCacheLayersItem>{
api::types::v1::RepositoryCacheLayersItem{ .info = {
.arch = std::vector<std::string>{ "x86_64" },
.channel = "main",
.id = "id1",
.kind = "runtime",
.packageInfoV2Module = "binary",
.version = "1.0.0",
} } }));
auto result = mockAction->testActionOperation(packageInfo, false);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->operation, service::ActionOperation::Policy::Install);
EXPECT_EQ(result->kind, "runtime");
EXPECT_EQ(result->oldRef, std::nullopt);
EXPECT_EQ(result->newRef->reference.toString(), "main:id1/2.0.0/x86_64");
}
TEST_F(ActionTest, InstallSameRuntime)
{
api::types::v1::PackageInfoV2 packageInfo = {
.arch = std::vector<std::string>{ "x86_64" },
.channel = "main",
.id = "id1",
.kind = "runtime",
.packageInfoV2Module = "binary",
.version = "1.0.0",
};
EXPECT_CALL(*repo, listLocalBy(_)).WillOnce(Return(std::vector<api::types::v1::RepositoryCacheLayersItem>{
api::types::v1::RepositoryCacheLayersItem{
.info = {
.arch = std::vector<std::string>{ "x86_64" },
.channel = "main",
.id = "id1",
.kind = "runtime",
.packageInfoV2Module = "binary",
.version = "2.0.0",
}
},
api::types::v1::RepositoryCacheLayersItem{
.info = {
.arch = std::vector<std::string>{ "x86_64" },
.channel = "main",
.id = "id1",
.kind = "runtime",
.packageInfoV2Module = "binary",
.version = "1.0.0",
}
}
}));
auto result = mockAction->testActionOperation(packageInfo, false);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->operation, service::ActionOperation::Policy::Overwrite);
EXPECT_EQ(result->kind, "runtime");
EXPECT_EQ(result->oldRef->toString(), "main:id1/1.0.0/x86_64");
EXPECT_EQ(result->newRef->reference.toString(), "main:id1/1.0.0/x86_64");
}

View File

@ -249,137 +249,6 @@ TEST(CheckUABLayersConstrain, ExtraModuleWithBinary)
EXPECT_TRUE(result.has_value());
}
TEST(GetTaskAction, InstallNewApp)
{
LINGLONG_TRACE("InstallNewApp");
TempDir tempDir;
MockOSTreeRepo mockRepo(tempDir.path());
repo::OSTreeRepo &repo = mockRepo;
std::vector<api::types::v1::UabLayer> appLayers = {
create_layer("id1", "1.0.0", "main", "app")
};
std::vector<api::types::v1::UabLayer> otherLayers;
EXPECT_CALL(mockRepo, latestLocalReference(_)).WillOnce(Return(LINGLONG_ERR("")));
auto result =
UabInstallationAction::getTaskAction(repo, std::make_pair(appLayers, otherLayers));
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->policy, service::TaskAction::Policy::Install);
}
TEST(GetTaskAction, OverwriteApp)
{
TempDir tempDir;
MockOSTreeRepo mockRepo(tempDir.path());
repo::OSTreeRepo &repo = mockRepo;
auto currentArch = package::Architecture::currentCPUArchitecture()->toStdString();
std::vector<api::types::v1::UabLayer> appLayers = {
create_layer("id1", "1.0.0", "main", "app")
};
std::vector<api::types::v1::UabLayer> otherLayers;
auto localRef = package::Reference::parse("main:id1/1.0.0/" + currentArch);
ASSERT_TRUE(localRef.has_value());
EXPECT_CALL(mockRepo, latestLocalReference(_)).WillOnce(Return(*localRef));
auto result =
UabInstallationAction::getTaskAction(repo, std::make_pair(appLayers, otherLayers));
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->policy, service::TaskAction::Policy::Overwrite);
}
TEST(GetTaskAction, UpgradeApp)
{
TempDir tempDir;
MockOSTreeRepo mockRepo(tempDir.path());
repo::OSTreeRepo &repo = mockRepo;
auto currentArch = package::Architecture::currentCPUArchitecture()->toStdString();
std::vector<api::types::v1::UabLayer> appLayers = {
create_layer("id1", "2.0.0", "main", "app")
};
std::vector<api::types::v1::UabLayer> otherLayers;
auto localRef = package::Reference::parse("main:id1/1.0.0/" + currentArch);
ASSERT_TRUE(localRef.has_value());
EXPECT_CALL(mockRepo, latestLocalReference(_)).WillOnce(Return(*localRef));
auto result =
UabInstallationAction::getTaskAction(repo, std::make_pair(appLayers, otherLayers));
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->policy, service::TaskAction::Policy::Upgrade);
}
TEST(GetTaskAction, DowngradeApp)
{
TempDir tempDir;
MockOSTreeRepo mockRepo(tempDir.path());
repo::OSTreeRepo &repo = mockRepo;
auto currentArch = package::Architecture::currentCPUArchitecture()->toStdString();
std::vector<api::types::v1::UabLayer> appLayers = {
create_layer("id1", "1.0.0", "main", "app")
};
std::vector<api::types::v1::UabLayer> otherLayers;
auto localRef = package::Reference::parse("main:id1/2.0.0/" + currentArch);
ASSERT_TRUE(localRef.has_value());
EXPECT_CALL(mockRepo, latestLocalReference(_)).WillOnce(Return(*localRef));
auto result =
UabInstallationAction::getTaskAction(repo, std::make_pair(appLayers, otherLayers));
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->policy, service::TaskAction::Policy::Downgrade);
}
TEST(GetTaskAction, InstallLowVersionRuntime)
{
TempDir tempDir;
MockOSTreeRepo mockRepo(tempDir.path());
repo::OSTreeRepo &repo = mockRepo;
auto currentArch = package::Architecture::currentCPUArchitecture()->toStdString();
std::vector<api::types::v1::UabLayer> appLayers;
std::vector<api::types::v1::UabLayer> otherLayers = {
create_layer("runtime.id1", "2.0.0", "main", "runtime")
};
auto localRef = package::Reference::parse("main:runtime.id1/1.0.0/" + currentArch);
ASSERT_TRUE(localRef.has_value());
EXPECT_CALL(mockRepo, latestLocalReference(_)).WillOnce(Return(*localRef));
auto result =
UabInstallationAction::getTaskAction(repo, std::make_pair(appLayers, otherLayers));
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->policy, service::TaskAction::Policy::Install);
}
TEST(GetTaskAction, InstallHighVersionRuntime)
{
TempDir tempDir;
MockOSTreeRepo mockRepo(tempDir.path());
repo::OSTreeRepo &repo = mockRepo;
auto currentArch = package::Architecture::currentCPUArchitecture()->toStdString();
std::vector<api::types::v1::UabLayer> appLayers;
std::vector<api::types::v1::UabLayer> otherLayers = {
create_layer("runtime.id1", "1.0.0", "main", "runtime")
};
auto localRef = package::Reference::parse("main:runtime.id1/2.0.0/" + currentArch);
ASSERT_TRUE(localRef.has_value());
EXPECT_CALL(mockRepo, latestLocalReference(_)).WillOnce(Return(*localRef));
auto result =
UabInstallationAction::getTaskAction(repo, std::make_pair(appLayers, otherLayers));
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->policy, service::TaskAction::Policy::Install);
}
// checkExecModeUABLayers tests
TEST(CheckExecModeUABLayers, NoAppLayers)
{

View File

@ -430,6 +430,249 @@ TEST(OSTreeRepoTest, searchRemote_RemoteError)
EXPECT_FALSE(result.has_value());
}
namespace {
class OSTreeRepoMock : public repo::OSTreeRepo
{
public:
OSTreeRepoMock(const std::filesystem::path &path)
: repo::OSTreeRepo(
QDir(path.c_str()),
api::types::v1::RepoConfigV2{ .defaultRepo = "", .repos = {}, .version = 2 })
{
}
MOCK_METHOD(utils::error::Result<std::vector<api::types::v1::PackageInfoV2>>,
searchRemote,
(const package::FuzzyReference &fuzzyRef,
const api::types::v1::Repo &repo,
bool semanticMatching),
(override, const, noexcept));
MOCK_METHOD(std::vector<std::vector<api::types::v1::Repo>>,
getPriorityGroupedRepos,
(),
(override, const, noexcept));
};
TEST(OSTreeRepoTest, matchRemoteByPriority_SpecifiedRepo)
{
TempDir tempDir;
OSTreeRepoMock mockRepo(tempDir.path());
repo::OSTreeRepo &repo = mockRepo;
auto fuzzyRef = package::FuzzyReference::parse("com.example.app");
auto repoConfig = api::types::v1::Repo{ .name = "test", .url = "http://localhost:8080" };
EXPECT_CALL(mockRepo, searchRemote(_, _, true))
.WillOnce(Return(std::vector<api::types::v1::PackageInfoV2>{
api::types::v1::PackageInfoV2{ .id = "com.example.app", .version = "1.0.0" },
}));
auto result = repo.matchRemoteByPriority(*fuzzyRef, repoConfig);
EXPECT_TRUE(result.has_value());
EXPECT_FALSE(result->empty());
const auto &repoPackages = result->getRepoPackages();
EXPECT_EQ(repoPackages.size(), 1);
EXPECT_EQ(repoPackages.front().first.name, "test");
EXPECT_EQ(repoPackages.front().second[0].id, "com.example.app");
EXPECT_EQ(repoPackages.front().second[0].version, "1.0.0");
}
TEST(OSTreeRepoTest, matchRemoteByPriority_NoRepoSpecified_Success)
{
TempDir tempDir;
OSTreeRepoMock mockRepo(tempDir.path());
repo::OSTreeRepo &repo = mockRepo;
auto fuzzyRef = package::FuzzyReference::parse("com.example.app");
EXPECT_CALL(mockRepo, getPriorityGroupedRepos())
.WillOnce(Return(std::vector<std::vector<api::types::v1::Repo>>{
std::vector<api::types::v1::Repo>{
api::types::v1::Repo{ .name = "repo1", .priority = 2, .url = "http://localhost:8081" },
},
std::vector<api::types::v1::Repo>{
api::types::v1::Repo{ .name = "repo2", .priority = 1, .url = "http://localhost:8081" },
api::types::v1::Repo{ .name = "repo3", .priority = 1, .url = "http://localhost:8082" } },
}));
EXPECT_CALL(mockRepo, searchRemote(_, _, true))
.WillOnce(Return(std::vector<api::types::v1::PackageInfoV2>{
api::types::v1::PackageInfoV2{ .id = "com.example.app", .version = "1.0.0" },
}));
auto result = repo.matchRemoteByPriority(*fuzzyRef);
EXPECT_TRUE(result.has_value());
EXPECT_FALSE(result->empty());
const auto &repoPackages = result->getRepoPackages();
EXPECT_EQ(repoPackages.size(), 1);
EXPECT_EQ(repoPackages.front().first.name, "repo1");
EXPECT_EQ(repoPackages.front().second[0].id, "com.example.app");
EXPECT_EQ(repoPackages.front().second[0].version, "1.0.0");
}
TEST(OSTreeRepoTest, matchRemoteByPriority_FallbackToLowerPriority)
{
TempDir tempDir;
OSTreeRepoMock mockRepo(tempDir.path());
repo::OSTreeRepo &repo = mockRepo;
auto fuzzyRef = package::FuzzyReference::parse("com.example.app");
EXPECT_CALL(mockRepo, getPriorityGroupedRepos())
.WillOnce(Return(std::vector<std::vector<api::types::v1::Repo>>{
std::vector<api::types::v1::Repo>{
api::types::v1::Repo{ .name = "repo1", .priority = 2, .url = "http://localhost:8081" },
},
std::vector<api::types::v1::Repo>{
api::types::v1::Repo{ .name = "repo2", .priority = 1, .url = "http://localhost:8081" },
api::types::v1::Repo{ .name = "repo3", .priority = 1, .url = "http://localhost:8082" } },
}));
EXPECT_CALL(mockRepo, searchRemote(_, _, true))
.WillOnce(Return(std::vector<api::types::v1::PackageInfoV2>{}))
.WillOnce(Return(std::vector<api::types::v1::PackageInfoV2>{
api::types::v1::PackageInfoV2{ .id = "com.example.app", .version = "1.0.0" },
}))
.WillOnce(Return(std::vector<api::types::v1::PackageInfoV2>{}));
auto result = repo.matchRemoteByPriority(*fuzzyRef);
EXPECT_TRUE(result.has_value());
EXPECT_FALSE(result->empty());
const auto &repoPackages = result->getRepoPackages();
EXPECT_EQ(repoPackages.size(), 1);
EXPECT_EQ(repoPackages.front().first.name, "repo2");
EXPECT_EQ(repoPackages.front().second[0].id, "com.example.app");
EXPECT_EQ(repoPackages.front().second[0].version, "1.0.0");
}
TEST(OSTreeRepoTest, matchRemoteByPriority_AllReposEmpty)
{
TempDir tempDir;
OSTreeRepoMock mockRepo(tempDir.path());
repo::OSTreeRepo &repo = mockRepo;
auto fuzzyRef = package::FuzzyReference::parse("com.example.app");
EXPECT_CALL(mockRepo, getPriorityGroupedRepos())
.WillOnce(Return(std::vector<std::vector<api::types::v1::Repo>>{
std::vector<api::types::v1::Repo>{
api::types::v1::Repo{ .name = "repo1", .priority = 2, .url = "http://localhost:8081" },
},
std::vector<api::types::v1::Repo>{
api::types::v1::Repo{ .name = "repo2", .priority = 1, .url = "http://localhost:8081" },
api::types::v1::Repo{ .name = "repo3", .priority = 1, .url = "http://localhost:8082" } },
}));
EXPECT_CALL(mockRepo, searchRemote(_, _, true))
.WillOnce(Return(std::vector<api::types::v1::PackageInfoV2>{}))
.WillOnce(Return(std::vector<api::types::v1::PackageInfoV2>{}))
.WillOnce(Return(std::vector<api::types::v1::PackageInfoV2>{}));
auto result = repo.matchRemoteByPriority(*fuzzyRef);
EXPECT_TRUE(result.has_value());
EXPECT_TRUE(result->empty());
}
TEST(OSTreeRepoTest, matchRemoteByPriority_AllReposError)
{
LINGLONG_TRACE("matchRemoteByPriority_AllReposError");
TempDir tempDir;
OSTreeRepoMock mockRepo(tempDir.path());
repo::OSTreeRepo &repo = mockRepo;
auto fuzzyRef = package::FuzzyReference::parse("com.example.app");
EXPECT_CALL(mockRepo, getPriorityGroupedRepos())
.WillOnce(Return(std::vector<std::vector<api::types::v1::Repo>>{
std::vector<api::types::v1::Repo>{
api::types::v1::Repo{ .name = "repo1", .priority = 2, .url = "http://localhost:8081" },
},
std::vector<api::types::v1::Repo>{
api::types::v1::Repo{ .name = "repo2", .priority = 1, .url = "http://localhost:8081" },
api::types::v1::Repo{ .name = "repo3", .priority = 1, .url = "http://localhost:8082" } },
}));
EXPECT_CALL(mockRepo, searchRemote(_, _, true))
.WillOnce(Return(LINGLONG_ERR("error")))
.WillOnce(Return(LINGLONG_ERR("error")))
.WillOnce(Return(LINGLONG_ERR("error")));
auto result = repo.matchRemoteByPriority(*fuzzyRef);
EXPECT_TRUE(result.has_value());
EXPECT_TRUE(result->empty());
}
TEST(OSTreeRepoTest, matchRemoteByPriority_NoReposConfigured)
{
TempDir tempDir;
OSTreeRepoMock mockRepo(tempDir.path());
repo::OSTreeRepo &repo = mockRepo;
auto fuzzyRef = package::FuzzyReference::parse("com.example.app");
EXPECT_CALL(mockRepo, getPriorityGroupedRepos())
.WillOnce(Return(std::vector<std::vector<api::types::v1::Repo>>{}));
auto result = repo.matchRemoteByPriority(*fuzzyRef);
EXPECT_TRUE(result.has_value());
EXPECT_TRUE(result->empty());
}
TEST(OSTreeRepoTest, matchRemoteByPriority_UseHighestPriority)
{
TempDir tempDir;
OSTreeRepoMock mockRepo(tempDir.path());
repo::OSTreeRepo &repo = mockRepo;
auto fuzzyRef = package::FuzzyReference::parse("com.example.app");
EXPECT_CALL(mockRepo, getPriorityGroupedRepos())
.WillOnce(Return(std::vector<std::vector<api::types::v1::Repo>>{
std::vector<api::types::v1::Repo>{
api::types::v1::Repo{ .name = "repo1", .priority = 3, .url = "http://localhost:8081" },
},
std::vector<api::types::v1::Repo>{
api::types::v1::Repo{ .name = "repo2", .priority = 2, .url = "http://localhost:8081" },
api::types::v1::Repo{ .name = "repo3", .priority = 2, .url = "http://localhost:8082" } },
std::vector<api::types::v1::Repo>{
api::types::v1::Repo{ .name = "repo4", .priority = 1, .url = "http://localhost:8081" },
},
}));
EXPECT_CALL(mockRepo, searchRemote(_, _, true))
.WillOnce(Return(std::vector<api::types::v1::PackageInfoV2>{}))
.WillOnce(Return(std::vector<api::types::v1::PackageInfoV2>{
api::types::v1::PackageInfoV2{ .id = "com.example.app", .version = "2.0.0" },
}))
.WillOnce(Return(std::vector<api::types::v1::PackageInfoV2>{
api::types::v1::PackageInfoV2{ .id = "com.example.app", .version = "3.0.0" },
}));
auto result = repo.matchRemoteByPriority(*fuzzyRef, std::nullopt);
EXPECT_TRUE(result.has_value());
EXPECT_FALSE(result->empty());
const auto &repoPackages = result->getRepoPackages();
EXPECT_EQ(repoPackages.size(), 2);
EXPECT_EQ(repoPackages.front().first.name, "repo2");
EXPECT_EQ(repoPackages.front().second[0].id, "com.example.app");
EXPECT_EQ(repoPackages.front().second[0].version, "2.0.0");
EXPECT_EQ(repoPackages.back().first.name, "repo3");
EXPECT_EQ(repoPackages.back().second[0].id, "com.example.app");
EXPECT_EQ(repoPackages.back().second[0].version, "3.0.0");
}
} // namespace
} // namespace
} // namespace linglong::repo::test

View File

@ -14,7 +14,7 @@ int main(int argc, char **argv)
{
qputenv("QT_FORCE_STDERR_LOGGING", QByteArray("1"));
linglong::utils::global::installMessageHandler();
linglong::utils::global::initLinyapsLogSystem(argv[0]);
linglong::utils::global::initLinyapsLogSystem(linglong::utils::log::LogBackend::Console);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -8,7 +8,6 @@
#include "configure.h"
#include "linglong/common/strings.h"
#include "linglong/utils/log/log.h"
#include <qcoreapplication.h>
#include <qloggingcategory.h>
@ -158,7 +157,7 @@ LogBackend parseLogBackend(const char *backends)
} // namespace
void initLinyapsLogSystem(const char *command)
void initLinyapsLogSystem(linglong::utils::log::LogBackend backend)
{
LogLevel logLevel = LogLevel::Info;
LogBackend logBackend = LogBackend::None;
@ -172,13 +171,7 @@ void initLinyapsLogSystem(const char *command)
if (logBackendEnv) {
logBackend = parseLogBackend(logBackendEnv);
} else {
if (command == std::string("ll-builder")) {
logBackend = LogBackend::Console;
} else if (command == std::string("ll-cli")) {
logBackend = LogBackend::Journal;
} else if (command == std::string("ll-package-manager")) {
logBackend = LogBackend::Journal;
}
logBackend = backend;
if (isatty(STDERR_FILENO)) {
logBackend = logBackend | LogBackend::Console;

View File

@ -6,6 +6,8 @@
#pragma once
#include "linglong/utils/log/log.h"
#include <QObject>
#include <atomic>
@ -16,7 +18,7 @@ void applicationInitialize(bool appForceStderrLogging = false);
void installMessageHandler();
bool linglongInstalled();
void cancelAllTask() noexcept;
void initLinyapsLogSystem(const char *command);
void initLinyapsLogSystem(linglong::utils::log::LogBackend backend);
class GlobalTaskControl : public QObject
{