Skip to main content

dev_scripts/
commands.rs

1use std::{
2    fs::{remove_dir_all, write},
3    path::PathBuf,
4    process::exit,
5};
6
7use alpm_common::MetadataFile;
8use alpm_srcinfo::{SourceInfo, SourceInfoV1};
9use log::warn;
10use serde_json::to_string_pretty;
11use strum::IntoEnumIterator;
12
13use crate::{
14    CacheDir,
15    Error,
16    cli::{CleanTarget, DownloadCmd, TestFileFormat},
17    consts::{DATABASES_DIR, DOWNLOAD_DIR, PACKAGES_DIR, PKGSRC_DIR},
18    sync::{
19        PackageRepositories,
20        aur::AurDownloader,
21        mirror::MirrorDownloader,
22        pkgsrc::PkgSrcDownloader,
23    },
24    testing::TestRunner,
25};
26
27/// Runs tests for a specific type of testing file format handling binaries for official ArchLinux
28/// packages, source repositories and databases.
29///
30/// This function relegates to functions that:
31/// - Download packages.
32/// - Test file parsers on all files.
33/// - Clean up downloaded files.
34pub(crate) fn test_files(
35    cache_dir: CacheDir,
36    file_type: TestFileFormat,
37    repositories: Option<Vec<PackageRepositories>>,
38) -> Result<(), Error> {
39    let runner = TestRunner {
40        cache_dir,
41        file_type,
42        repositories,
43    };
44    runner.run_tests()?;
45
46    Ok(())
47}
48
49/// Downloads a specific type of testing file source
50pub(crate) fn download_files(
51    cache_dir: CacheDir,
52    source: DownloadCmd,
53    repositories: Option<Vec<PackageRepositories>>,
54) -> Result<(), Error> {
55    let repositories = PackageRepositories::iter()
56        .filter(|v| repositories.clone().is_none_or(|r| r.contains(v)))
57        .collect();
58
59    match source {
60        DownloadCmd::PkgSrcRepositories {} => {
61            let downloader = PkgSrcDownloader { cache_dir };
62            downloader.download_package_source_repositories()?;
63        }
64        DownloadCmd::Aur {} => {
65            let downloader = AurDownloader { cache_dir };
66            downloader.download_packages()?;
67        }
68        DownloadCmd::Databases {
69            mirror,
70            force_extract,
71        } => {
72            let downloader = MirrorDownloader {
73                cache_dir,
74                mirror,
75                repositories,
76                extract_all: force_extract,
77            };
78            warn!(
79                "Beginning database retrieval\nIf the process is unexpectedly halted, rerun with `--force-extract` flag"
80            );
81            downloader.sync_remote_databases()?;
82        }
83        DownloadCmd::Packages {
84            mirror,
85            force_extract,
86        } => {
87            let downloader = MirrorDownloader {
88                cache_dir,
89                mirror,
90                repositories,
91                extract_all: force_extract,
92            };
93            warn!(
94                "Beginning package retrieval\nIf the process is unexpectedly halted, rerun with `--force-extract` flag"
95            );
96            downloader.sync_remote_packages()?;
97        }
98    }
99
100    Ok(())
101}
102
103/// Cleans up a specific type of test files.
104pub(crate) fn clean_files(target: CleanTarget, cache_dir: CacheDir) -> Result<(), Error> {
105    let dirs = match target {
106        CleanTarget::PkgSrcRepositories => [
107            cache_dir.as_ref().join(DOWNLOAD_DIR).join(PKGSRC_DIR),
108            cache_dir.as_ref().join(PKGSRC_DIR),
109        ],
110        CleanTarget::Databases => [
111            cache_dir.as_ref().join(DOWNLOAD_DIR).join(DATABASES_DIR),
112            cache_dir.as_ref().join(DATABASES_DIR),
113        ],
114        CleanTarget::Packages => [
115            cache_dir.as_ref().join(DOWNLOAD_DIR).join(PACKAGES_DIR),
116            cache_dir.as_ref().join(PACKAGES_DIR),
117        ],
118    };
119
120    for dir in dirs {
121        remove_dir_all(&dir).map_err(|source| Error::IoPath {
122            path: dir,
123            context: "recursively deleting the directory".to_string(),
124            source,
125        })?;
126    }
127
128    Ok(())
129}
130
131/// Run the `alpm-pkgbuild srcinfo format` command on a PKGBUILD and compare its output with a
132/// given .SRCINFO file.
133///
134/// If the generated and read [`SRCINFO`] representations do not match, the respective files
135/// `pkgbuild.json` and `srcinfo.json` are output to the current working directory and the function
136/// exits with an exit code of `1`.
137///
138/// These files contain pretty-printed JSON, which accurately depicts the internal representation
139/// used to compare the two files.
140///
141/// # Errors
142///
143/// Returns an error if
144///
145/// - running the [`alpm-pkgbuild-bridge`] script fails,
146/// - creating a [`SourceInfoV1`] from the script output fails,
147/// - creating a [`SourceInfo`] from the the [`SRCINFO`] file fails,
148/// - or creating JSON representations for either [`SRCINFO`] data fails in case of mismatch.
149///
150/// [`PKGBUILD`]: https://man.archlinux.org/man/PKGBUILD.5
151/// [`SRCINFO`]: https://alpm.archlinux.page/specifications/SRCINFO.5.html
152/// [`alpm-pkgbuild-bridge`]: https://gitlab.archlinux.org/archlinux/alpm/alpm-pkgbuild-bridge
153pub fn compare_source_info(pkgbuild_path: PathBuf, srcinfo_path: PathBuf) -> Result<(), Error> {
154    let pkgbuild_source_info: SourceInfoV1 = SourceInfoV1::from_pkgbuild(&pkgbuild_path)?;
155
156    let source_info = SourceInfo::from_file_with_schema(srcinfo_path, None)?;
157    let SourceInfo::V1(source_info) = source_info;
158
159    if source_info != pkgbuild_source_info {
160        let pkgbuild_source_info =
161            to_string_pretty(&pkgbuild_source_info).map_err(|source| Error::Json {
162                context: "deserializing a PKGBUILD  based SRCINFO as pretty JSON".to_string(),
163                source,
164            })?;
165        let source_info = to_string_pretty(&source_info).map_err(|source| Error::Json {
166            context: "deserializing a SRCINFO as pretty JSON".to_string(),
167            source,
168        })?;
169
170        let pkgbuild_json_path = PathBuf::from("pkgbuild.json");
171        write("pkgbuild.json", pkgbuild_source_info).map_err(|source| Error::IoPath {
172            path: pkgbuild_json_path,
173            context: "writing pkgbuild.json file".to_string(),
174            source,
175        })?;
176        let srcinfo_json_path = PathBuf::from("srcinfo.json");
177        write("srcinfo.json", source_info).map_err(|source| Error::IoPath {
178            path: srcinfo_json_path,
179            context: "writing srcinfo.json file".to_string(),
180            source,
181        })?;
182
183        eprintln!(
184            "SRCINFO data generated from PKGBUILD file differs from the .SRCINFO file read from disk.\n\
185            Compare the two generated files pkgbuild.json and srcinfo.json for details."
186        );
187        exit(1);
188    } else {
189        println!("The generated content matches that read from disk.");
190    }
191
192    Ok(())
193}