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