dev_scripts/
commands.rs

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