1#![cfg_attr(doc_cfg, feature(doc_cfg))]
77#![deny(missing_docs)]
78
79use std::{io::Write, path::PathBuf};
80
81mod codesign;
82mod error;
83mod package;
84mod shell;
85mod util;
86
87#[cfg(feature = "cli")]
88#[cfg_attr(doc_cfg, doc(cfg(feature = "cli")))]
89pub mod cli;
90pub mod config;
91pub mod sign;
92
93pub use config::{Config, PackageFormat};
94pub use error::{Error, Result};
95use flate2::{write::GzEncoder, Compression};
96pub use sign::SigningConfig;
97
98pub use package::{package, PackageOutput};
99use util::PathExt;
100
101#[cfg(feature = "cli")]
102fn parse_log_level(verbose: u8) -> tracing::Level {
103 match verbose {
104 0 => tracing_subscriber::EnvFilter::builder()
105 .from_env_lossy()
106 .max_level_hint()
107 .and_then(|l| l.into_level())
108 .unwrap_or(tracing::Level::INFO),
109 1 => tracing::Level::DEBUG,
110 2.. => tracing::Level::TRACE,
111 }
112}
113
114#[cfg(feature = "cli")]
116#[cfg_attr(doc_cfg, doc(cfg(feature = "cli")))]
117pub fn init_tracing_subscriber(verbosity: u8) {
118 let level = parse_log_level(verbosity);
119
120 let debug = level == tracing::Level::DEBUG;
121 let tracing = level == tracing::Level::TRACE;
122
123 let subscriber = tracing_subscriber::fmt()
124 .with_ansi(std::io::IsTerminal::is_terminal(&std::io::stderr()))
125 .with_target(debug)
126 .with_line_number(tracing)
127 .with_file(tracing)
128 .with_max_level(level);
129
130 let formatter = tracing_subscriber::fmt::format()
131 .compact()
132 .with_target(debug)
133 .with_line_number(tracing)
134 .with_file(tracing);
135
136 if tracing {
137 subscriber
138 .event_format(TracingFormatter::WithTime(formatter))
139 .init();
140 } else {
141 subscriber
142 .without_time()
143 .event_format(TracingFormatter::WithoutTime(formatter.without_time()))
144 .init();
145 }
146}
147
148#[cfg(feature = "cli")]
149enum TracingFormatter {
150 WithoutTime(
151 tracing_subscriber::fmt::format::Format<tracing_subscriber::fmt::format::Compact, ()>,
152 ),
153 WithTime(tracing_subscriber::fmt::format::Format<tracing_subscriber::fmt::format::Compact>),
154}
155
156#[cfg(feature = "cli")]
157struct ShellFieldVisitor {
158 message: String,
159}
160
161#[cfg(feature = "cli")]
162impl tracing::field::Visit for ShellFieldVisitor {
163 fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
164 if field.name() == "message" {
165 self.message = value.to_string();
166 }
167 }
168
169 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
170 if field.name() == "message" {
171 self.message = format!("{value:?}");
172 }
173 }
174}
175
176#[cfg(feature = "cli")]
177impl<S, N> tracing_subscriber::fmt::FormatEvent<S, N> for TracingFormatter
178where
179 S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
180 N: for<'a> tracing_subscriber::fmt::FormatFields<'a> + 'static,
181{
182 fn format_event(
183 &self,
184 ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>,
185 mut writer: tracing_subscriber::fmt::format::Writer<'_>,
186 event: &tracing::Event<'_>,
187 ) -> std::fmt::Result {
188 if event.fields().any(|f| f.name() == "shell") {
189 let mut visitor = ShellFieldVisitor { message: "".into() };
190 event.record(&mut visitor);
191 writeln!(writer, "{}", visitor.message)
192 } else {
193 match self {
194 TracingFormatter::WithoutTime(formatter) => {
195 formatter.format_event(ctx, writer, event)
196 }
197 TracingFormatter::WithTime(formatter) => formatter.format_event(ctx, writer, event),
198 }
199 }
200 }
201}
202
203#[tracing::instrument(level = "trace")]
208pub fn sign_outputs(
209 config: &SigningConfig,
210 packages: &mut Vec<PackageOutput>,
211) -> crate::Result<Vec<PathBuf>> {
212 let mut signatures = Vec::new();
213 for package in packages {
214 for path in &package.paths.clone() {
215 let path = if path.is_dir() {
216 let zip = path.with_additional_extension("tar.gz");
217 let dest_file = util::create_file(&zip)?;
218 let gzip_encoder = GzEncoder::new(dest_file, Compression::default());
219 let writer = util::create_tar_from_dir(path, gzip_encoder)?;
220 let mut dest_file = writer.finish()?;
221 dest_file.flush()?;
222
223 package.paths.push(zip);
224 package.paths.last().unwrap()
225 } else {
226 path
227 };
228 signatures.push(sign::sign_file(config, path)?);
229 }
230 }
231
232 Ok(signatures)
233}
234
235#[tracing::instrument(level = "trace")]
242pub fn package_and_sign(
243 config: &Config,
244 signing_config: &SigningConfig,
245) -> crate::Result<(Vec<PackageOutput>, Vec<PathBuf>)> {
246 let mut packages = package(config)?;
247 let signatures = sign_outputs(signing_config, &mut packages)?;
248 Ok((packages, signatures))
249}