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