electron-cli 0.3.0-alpha.7 → 0.3.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +399 -3
- package/Cargo.toml +5 -2
- package/README.md +5 -3
- package/package.json +1 -1
- package/src/cli.rs +6 -2
- package/src/commands/make.rs +866 -10
- package/src/commands/package.rs +4 -0
- package/src/commands/plan.rs +1 -0
- package/src/project.rs +6 -0
package/src/commands/make.rs
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
use std::{
|
|
2
2
|
fs,
|
|
3
3
|
fs::File,
|
|
4
|
-
io::{self, BufWriter},
|
|
4
|
+
io::{self, BufWriter, Write},
|
|
5
5
|
path::{Path, PathBuf},
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
use anyhow::{bail, Context, Result};
|
|
9
9
|
use camino::Utf8PathBuf;
|
|
10
|
+
use flate2::{write::GzEncoder, Compression};
|
|
11
|
+
use rpm::{BuildConfig, CompressionType, FileOptions, PackageBuilder};
|
|
10
12
|
use serde::Serialize;
|
|
13
|
+
use tar::{Builder as TarBuilder, Header as TarHeader};
|
|
11
14
|
use zip::{write::SimpleFileOptions, CompressionMethod, ZipWriter};
|
|
12
15
|
|
|
13
16
|
use crate::{
|
|
14
|
-
cli::{MakeArgs, PackageArgs},
|
|
17
|
+
cli::{MakeArgs, MakeTarget, PackageArgs},
|
|
15
18
|
commands::package::{self, PackageReport},
|
|
16
19
|
output,
|
|
17
20
|
};
|
|
@@ -67,14 +70,16 @@ pub(crate) fn build_report(args: &MakeArgs) -> Result<MakeReport> {
|
|
|
67
70
|
.join(args.target.as_str())
|
|
68
71
|
.join(package.platform())
|
|
69
72
|
.join(package.arch());
|
|
70
|
-
let artifact = make_dir.
|
|
71
|
-
"{}-{}-{}.zip",
|
|
72
|
-
package.artifact_stem(),
|
|
73
|
-
package.platform(),
|
|
74
|
-
package.arch()
|
|
75
|
-
));
|
|
73
|
+
let artifact = make_artifact_path(&make_dir, &package, args.target);
|
|
76
74
|
|
|
77
75
|
let mut warnings = package.warnings().to_vec();
|
|
76
|
+
if matches!(args.target, MakeTarget::Deb | MakeTarget::Rpm) && package.platform() != "linux" {
|
|
77
|
+
warnings.push(format!(
|
|
78
|
+
"{} maker only supports linux packages; target platform is {}.",
|
|
79
|
+
args.target.as_str(),
|
|
80
|
+
package.platform()
|
|
81
|
+
));
|
|
82
|
+
}
|
|
78
83
|
if args.skip_package && !Path::new(package.bundle_dir().as_str()).exists() {
|
|
79
84
|
warnings.push(format!(
|
|
80
85
|
"Package output does not exist: {}.",
|
|
@@ -128,7 +133,13 @@ pub(crate) fn execute_make(report: &mut MakeReport, args: &MakeArgs) -> Result<(
|
|
|
128
133
|
|
|
129
134
|
fs::create_dir_all(report.make_dir.as_str())
|
|
130
135
|
.with_context(|| format!("Could not create {}", report.make_dir))?;
|
|
131
|
-
|
|
136
|
+
match args.target {
|
|
137
|
+
MakeTarget::Zip => {
|
|
138
|
+
write_zip_archive(Path::new(report.package.bundle_dir().as_str()), artifact)?
|
|
139
|
+
}
|
|
140
|
+
MakeTarget::Deb => write_deb_archive(&report.package, artifact)?,
|
|
141
|
+
MakeTarget::Rpm => write_rpm_archive(&report.package, artifact)?,
|
|
142
|
+
}
|
|
132
143
|
|
|
133
144
|
Ok(())
|
|
134
145
|
}
|
|
@@ -173,6 +184,29 @@ fn print_report(report: &MakeReport, json: bool) -> Result<()> {
|
|
|
173
184
|
Ok(())
|
|
174
185
|
}
|
|
175
186
|
|
|
187
|
+
fn make_artifact_path(make_dir: &Path, package: &PackageReport, target: MakeTarget) -> PathBuf {
|
|
188
|
+
match target {
|
|
189
|
+
MakeTarget::Zip => make_dir.join(format!(
|
|
190
|
+
"{}-{}-{}.zip",
|
|
191
|
+
package.artifact_stem(),
|
|
192
|
+
package.platform(),
|
|
193
|
+
package.arch()
|
|
194
|
+
)),
|
|
195
|
+
MakeTarget::Deb => make_dir.join(format!(
|
|
196
|
+
"{}_{}_{}.deb",
|
|
197
|
+
debian_package_name(&package.artifact_stem()),
|
|
198
|
+
debian_version(package.project().version.as_deref()),
|
|
199
|
+
debian_arch(package.arch())
|
|
200
|
+
)),
|
|
201
|
+
MakeTarget::Rpm => make_dir.join(format!(
|
|
202
|
+
"{}-{}-1.{}.rpm",
|
|
203
|
+
rpm_package_name(&package.artifact_stem()),
|
|
204
|
+
rpm_version(package.project().version.as_deref()),
|
|
205
|
+
rpm_arch(package.arch())
|
|
206
|
+
)),
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
176
210
|
fn write_zip_archive(source: &Path, artifact: &Path) -> Result<()> {
|
|
177
211
|
if !source.exists() {
|
|
178
212
|
bail!("Package output does not exist: {}", source.display());
|
|
@@ -263,6 +297,510 @@ fn directory_options(metadata: &fs::Metadata) -> SimpleFileOptions {
|
|
|
263
297
|
.unix_permissions(unix_mode(metadata, 0o755))
|
|
264
298
|
}
|
|
265
299
|
|
|
300
|
+
fn write_deb_archive(package: &PackageReport, artifact: &Path) -> Result<()> {
|
|
301
|
+
if package.platform() != "linux" {
|
|
302
|
+
bail!(
|
|
303
|
+
"Deb maker only supports linux packages. Requested {}.",
|
|
304
|
+
package.platform()
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
let source = Path::new(package.bundle_dir().as_str());
|
|
309
|
+
if !source.exists() {
|
|
310
|
+
bail!("Package output does not exist: {}", source.display());
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let parent = artifact
|
|
314
|
+
.parent()
|
|
315
|
+
.with_context(|| format!("Artifact path has no parent: {}", artifact.display()))?;
|
|
316
|
+
fs::create_dir_all(parent).with_context(|| format!("Could not create {}", parent.display()))?;
|
|
317
|
+
|
|
318
|
+
let deb_package = debian_package_name(&package.artifact_stem());
|
|
319
|
+
let version = debian_version(package.project().version.as_deref());
|
|
320
|
+
let arch = debian_arch(package.arch());
|
|
321
|
+
let installed_size = directory_size(source)?.div_ceil(1024).max(1);
|
|
322
|
+
let control = debian_control_file(package, &deb_package, &version, &arch, installed_size);
|
|
323
|
+
let control_tar =
|
|
324
|
+
gzip_tar(|builder| append_bytes_to_tar(builder, "./control", control.as_bytes(), 0o644))?;
|
|
325
|
+
let data_tar = gzip_tar(|builder| append_deb_data_tar(builder, package, source, &deb_package))?;
|
|
326
|
+
|
|
327
|
+
write_ar_archive(
|
|
328
|
+
artifact,
|
|
329
|
+
&[
|
|
330
|
+
ArMember {
|
|
331
|
+
name: "debian-binary",
|
|
332
|
+
mode: 0o100644,
|
|
333
|
+
data: b"2.0\n".to_vec(),
|
|
334
|
+
},
|
|
335
|
+
ArMember {
|
|
336
|
+
name: "control.tar.gz",
|
|
337
|
+
mode: 0o100644,
|
|
338
|
+
data: control_tar,
|
|
339
|
+
},
|
|
340
|
+
ArMember {
|
|
341
|
+
name: "data.tar.gz",
|
|
342
|
+
mode: 0o100644,
|
|
343
|
+
data: data_tar,
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
fn write_rpm_archive(package: &PackageReport, artifact: &Path) -> Result<()> {
|
|
350
|
+
if package.platform() != "linux" {
|
|
351
|
+
bail!(
|
|
352
|
+
"RPM maker only supports linux packages. Requested {}.",
|
|
353
|
+
package.platform()
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
let source = Path::new(package.bundle_dir().as_str());
|
|
358
|
+
if !source.exists() {
|
|
359
|
+
bail!("Package output does not exist: {}", source.display());
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
let parent = artifact
|
|
363
|
+
.parent()
|
|
364
|
+
.with_context(|| format!("Artifact path has no parent: {}", artifact.display()))?;
|
|
365
|
+
fs::create_dir_all(parent).with_context(|| format!("Could not create {}", parent.display()))?;
|
|
366
|
+
|
|
367
|
+
let rpm_package = rpm_package_name(&package.artifact_stem());
|
|
368
|
+
let version = rpm_version(package.project().version.as_deref());
|
|
369
|
+
let arch = rpm_arch(package.arch());
|
|
370
|
+
let executable = format!("/opt/{rpm_package}/{}", package.executable_name());
|
|
371
|
+
let mut builder = PackageBuilder::new(
|
|
372
|
+
&rpm_package,
|
|
373
|
+
&version,
|
|
374
|
+
package
|
|
375
|
+
.project()
|
|
376
|
+
.license
|
|
377
|
+
.as_deref()
|
|
378
|
+
.unwrap_or("LicenseRef-unknown"),
|
|
379
|
+
&arch,
|
|
380
|
+
&single_line(package.app_name()),
|
|
381
|
+
);
|
|
382
|
+
builder
|
|
383
|
+
.using_config(
|
|
384
|
+
BuildConfig::v4()
|
|
385
|
+
.compression(CompressionType::Gzip)
|
|
386
|
+
.reserved_space(None)
|
|
387
|
+
.source_date(0),
|
|
388
|
+
)
|
|
389
|
+
.release("1")
|
|
390
|
+
.vendor("electron-cli")
|
|
391
|
+
.packager("electron-cli")
|
|
392
|
+
.description(format!(
|
|
393
|
+
"{} packaged by electron-cli.",
|
|
394
|
+
single_line(package.app_name())
|
|
395
|
+
))
|
|
396
|
+
.default_file_attrs(None, Some("root".to_string()), Some("root".to_string()))
|
|
397
|
+
.default_dir_attrs(None, Some("root".to_string()), Some("root".to_string()));
|
|
398
|
+
|
|
399
|
+
for directory in [
|
|
400
|
+
"/opt",
|
|
401
|
+
"/usr",
|
|
402
|
+
"/usr/bin",
|
|
403
|
+
"/usr/share",
|
|
404
|
+
"/usr/share/applications",
|
|
405
|
+
] {
|
|
406
|
+
builder.with_dir_entry(FileOptions::dir(directory).permissions(0o755))?;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
builder.with_dir(source, format!("/opt/{rpm_package}"), |options| options)?;
|
|
410
|
+
builder.with_symlink(FileOptions::symlink(
|
|
411
|
+
format!("/usr/bin/{rpm_package}"),
|
|
412
|
+
&executable,
|
|
413
|
+
))?;
|
|
414
|
+
builder.with_file_contents(
|
|
415
|
+
rpm_desktop_file(package, &rpm_package, &executable),
|
|
416
|
+
FileOptions::new(format!("/usr/share/applications/{rpm_package}.desktop"))
|
|
417
|
+
.permissions(0o644),
|
|
418
|
+
)?;
|
|
419
|
+
|
|
420
|
+
let rpm = builder.build()?;
|
|
421
|
+
rpm.write_file(artifact)
|
|
422
|
+
.with_context(|| format!("Could not write {}", artifact.display()))
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
fn debian_control_file(
|
|
426
|
+
package: &PackageReport,
|
|
427
|
+
deb_package: &str,
|
|
428
|
+
version: &str,
|
|
429
|
+
arch: &str,
|
|
430
|
+
installed_size: u64,
|
|
431
|
+
) -> String {
|
|
432
|
+
format!(
|
|
433
|
+
"Package: {deb_package}\n\
|
|
434
|
+
Version: {version}\n\
|
|
435
|
+
Section: utils\n\
|
|
436
|
+
Priority: optional\n\
|
|
437
|
+
Architecture: {arch}\n\
|
|
438
|
+
Maintainer: electron-cli <noreply@example.invalid>\n\
|
|
439
|
+
Installed-Size: {installed_size}\n\
|
|
440
|
+
Description: {description}\n\
|
|
441
|
+
Electron application packaged by electron-cli.\n",
|
|
442
|
+
description = single_line(package.app_name())
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
fn append_deb_data_tar(
|
|
447
|
+
builder: &mut TarBuilder<GzEncoder<Vec<u8>>>,
|
|
448
|
+
package: &PackageReport,
|
|
449
|
+
source: &Path,
|
|
450
|
+
deb_package: &str,
|
|
451
|
+
) -> Result<()> {
|
|
452
|
+
for directory in [
|
|
453
|
+
"./",
|
|
454
|
+
"./opt",
|
|
455
|
+
"./usr",
|
|
456
|
+
"./usr/bin",
|
|
457
|
+
"./usr/share",
|
|
458
|
+
"./usr/share/applications",
|
|
459
|
+
] {
|
|
460
|
+
append_directory_to_tar(builder, directory, 0o755)?;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
let app_root = format!("./opt/{deb_package}");
|
|
464
|
+
append_directory_to_tar(builder, &app_root, 0o755)?;
|
|
465
|
+
append_directory_contents_to_tar(builder, source, Path::new(&app_root))?;
|
|
466
|
+
|
|
467
|
+
let executable = format!("/opt/{deb_package}/{}", package.executable_name());
|
|
468
|
+
append_symlink_to_tar(
|
|
469
|
+
builder,
|
|
470
|
+
format!("./usr/bin/{deb_package}"),
|
|
471
|
+
&executable,
|
|
472
|
+
0o777,
|
|
473
|
+
)?;
|
|
474
|
+
append_bytes_to_tar(
|
|
475
|
+
builder,
|
|
476
|
+
format!("./usr/share/applications/{deb_package}.desktop"),
|
|
477
|
+
debian_desktop_file(package, deb_package, &executable).as_bytes(),
|
|
478
|
+
0o644,
|
|
479
|
+
)?;
|
|
480
|
+
|
|
481
|
+
Ok(())
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
fn debian_desktop_file(package: &PackageReport, deb_package: &str, executable: &str) -> String {
|
|
485
|
+
desktop_file(package, deb_package, executable)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
fn rpm_desktop_file(package: &PackageReport, rpm_package: &str, executable: &str) -> String {
|
|
489
|
+
desktop_file(package, rpm_package, executable)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
fn desktop_file(package: &PackageReport, package_name: &str, executable: &str) -> String {
|
|
493
|
+
format!(
|
|
494
|
+
"[Desktop Entry]\n\
|
|
495
|
+
Name={name}\n\
|
|
496
|
+
Exec={executable} %U\n\
|
|
497
|
+
Terminal=false\n\
|
|
498
|
+
Type=Application\n\
|
|
499
|
+
StartupWMClass={wm_class}\n\
|
|
500
|
+
Categories=Utility;\n",
|
|
501
|
+
name = single_line(package.app_name()),
|
|
502
|
+
wm_class = package_name
|
|
503
|
+
)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
fn gzip_tar(
|
|
507
|
+
write_contents: impl FnOnce(&mut TarBuilder<GzEncoder<Vec<u8>>>) -> Result<()>,
|
|
508
|
+
) -> Result<Vec<u8>> {
|
|
509
|
+
let encoder = GzEncoder::new(Vec::new(), Compression::default());
|
|
510
|
+
let mut builder = TarBuilder::new(encoder);
|
|
511
|
+
builder.mode(tar::HeaderMode::Deterministic);
|
|
512
|
+
write_contents(&mut builder)?;
|
|
513
|
+
builder.finish().context("Could not finish tar archive")?;
|
|
514
|
+
let encoder = builder
|
|
515
|
+
.into_inner()
|
|
516
|
+
.context("Could not retrieve gzip encoder")?;
|
|
517
|
+
encoder.finish().context("Could not finish gzip archive")
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
fn append_directory_contents_to_tar(
|
|
521
|
+
builder: &mut TarBuilder<GzEncoder<Vec<u8>>>,
|
|
522
|
+
source: &Path,
|
|
523
|
+
destination: &Path,
|
|
524
|
+
) -> Result<()> {
|
|
525
|
+
let mut entries = fs::read_dir(source)
|
|
526
|
+
.with_context(|| format!("Could not read {}", source.display()))?
|
|
527
|
+
.collect::<Result<Vec<_>, io::Error>>()?;
|
|
528
|
+
entries.sort_by_key(|entry| entry.path());
|
|
529
|
+
|
|
530
|
+
for entry in entries {
|
|
531
|
+
let source_path = entry.path();
|
|
532
|
+
let destination_path = destination.join(entry.file_name());
|
|
533
|
+
append_path_to_tar(builder, &source_path, &destination_path)?;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
Ok(())
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
fn append_path_to_tar(
|
|
540
|
+
builder: &mut TarBuilder<GzEncoder<Vec<u8>>>,
|
|
541
|
+
source: &Path,
|
|
542
|
+
destination: &Path,
|
|
543
|
+
) -> Result<()> {
|
|
544
|
+
let metadata = fs::symlink_metadata(source)
|
|
545
|
+
.with_context(|| format!("Could not stat {}", source.display()))?;
|
|
546
|
+
|
|
547
|
+
if metadata.is_dir() {
|
|
548
|
+
append_directory_to_tar(builder, destination, unix_mode(&metadata, 0o755))?;
|
|
549
|
+
append_directory_contents_to_tar(builder, source, destination)?;
|
|
550
|
+
} else if metadata.file_type().is_symlink() {
|
|
551
|
+
let target = fs::read_link(source)
|
|
552
|
+
.with_context(|| format!("Could not read link {}", source.display()))?;
|
|
553
|
+
append_symlink_to_tar(builder, destination, &target, 0o777)?;
|
|
554
|
+
} else if metadata.is_file() {
|
|
555
|
+
append_file_to_tar(builder, source, destination, &metadata)?;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
Ok(())
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
fn append_directory_to_tar(
|
|
562
|
+
builder: &mut TarBuilder<GzEncoder<Vec<u8>>>,
|
|
563
|
+
path: impl AsRef<Path>,
|
|
564
|
+
mode: u32,
|
|
565
|
+
) -> Result<()> {
|
|
566
|
+
let mut header = TarHeader::new_gnu();
|
|
567
|
+
header.set_entry_type(tar::EntryType::Directory);
|
|
568
|
+
header.set_size(0);
|
|
569
|
+
header.set_mode(mode);
|
|
570
|
+
header.set_mtime(0);
|
|
571
|
+
header.set_cksum();
|
|
572
|
+
builder
|
|
573
|
+
.append_data(&mut header, path.as_ref(), io::empty())
|
|
574
|
+
.with_context(|| format!("Could not add {} to data tar", path.as_ref().display()))
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
fn append_file_to_tar(
|
|
578
|
+
builder: &mut TarBuilder<GzEncoder<Vec<u8>>>,
|
|
579
|
+
source: &Path,
|
|
580
|
+
destination: &Path,
|
|
581
|
+
metadata: &fs::Metadata,
|
|
582
|
+
) -> Result<()> {
|
|
583
|
+
let mut header = TarHeader::new_gnu();
|
|
584
|
+
header.set_entry_type(tar::EntryType::Regular);
|
|
585
|
+
header.set_size(metadata.len());
|
|
586
|
+
header.set_mode(unix_mode(metadata, 0o644));
|
|
587
|
+
header.set_mtime(0);
|
|
588
|
+
header.set_cksum();
|
|
589
|
+
let mut file =
|
|
590
|
+
File::open(source).with_context(|| format!("Could not open {}", source.display()))?;
|
|
591
|
+
builder
|
|
592
|
+
.append_data(&mut header, destination, &mut file)
|
|
593
|
+
.with_context(|| format!("Could not add {} to data tar", source.display()))
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
fn append_bytes_to_tar(
|
|
597
|
+
builder: &mut TarBuilder<GzEncoder<Vec<u8>>>,
|
|
598
|
+
path: impl AsRef<Path>,
|
|
599
|
+
contents: &[u8],
|
|
600
|
+
mode: u32,
|
|
601
|
+
) -> Result<()> {
|
|
602
|
+
let mut header = TarHeader::new_gnu();
|
|
603
|
+
header.set_entry_type(tar::EntryType::Regular);
|
|
604
|
+
header.set_size(contents.len() as u64);
|
|
605
|
+
header.set_mode(mode);
|
|
606
|
+
header.set_mtime(0);
|
|
607
|
+
header.set_cksum();
|
|
608
|
+
builder
|
|
609
|
+
.append_data(&mut header, path.as_ref(), contents)
|
|
610
|
+
.with_context(|| format!("Could not add {} to tar", path.as_ref().display()))
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
fn append_symlink_to_tar(
|
|
614
|
+
builder: &mut TarBuilder<GzEncoder<Vec<u8>>>,
|
|
615
|
+
path: impl AsRef<Path>,
|
|
616
|
+
target: impl AsRef<Path>,
|
|
617
|
+
mode: u32,
|
|
618
|
+
) -> Result<()> {
|
|
619
|
+
let mut header = TarHeader::new_gnu();
|
|
620
|
+
header.set_entry_type(tar::EntryType::Symlink);
|
|
621
|
+
header.set_size(0);
|
|
622
|
+
header.set_mode(mode);
|
|
623
|
+
header.set_mtime(0);
|
|
624
|
+
header
|
|
625
|
+
.set_link_name(target.as_ref())
|
|
626
|
+
.with_context(|| format!("Could not set link target for {}", path.as_ref().display()))?;
|
|
627
|
+
header.set_cksum();
|
|
628
|
+
builder
|
|
629
|
+
.append_data(&mut header, path.as_ref(), io::empty())
|
|
630
|
+
.with_context(|| format!("Could not add {} to tar", path.as_ref().display()))
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
struct ArMember {
|
|
634
|
+
name: &'static str,
|
|
635
|
+
mode: u32,
|
|
636
|
+
data: Vec<u8>,
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
fn write_ar_archive(artifact: &Path, members: &[ArMember]) -> Result<()> {
|
|
640
|
+
let mut file = BufWriter::new(
|
|
641
|
+
File::create(artifact)
|
|
642
|
+
.with_context(|| format!("Could not create {}", artifact.display()))?,
|
|
643
|
+
);
|
|
644
|
+
file.write_all(b"!<arch>\n")
|
|
645
|
+
.with_context(|| format!("Could not write {}", artifact.display()))?;
|
|
646
|
+
|
|
647
|
+
for member in members {
|
|
648
|
+
write_ar_member(&mut file, member)
|
|
649
|
+
.with_context(|| format!("Could not add {} to {}", member.name, artifact.display()))?;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
file.flush()
|
|
653
|
+
.with_context(|| format!("Could not finish {}", artifact.display()))
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
fn write_ar_member(writer: &mut impl Write, member: &ArMember) -> Result<()> {
|
|
657
|
+
let name = format!("{}/", member.name);
|
|
658
|
+
if name.len() > 16 {
|
|
659
|
+
bail!("ar member name is too long: {}", member.name);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
let header = format!(
|
|
663
|
+
"{name:<16}{mtime:<12}{uid:<6}{gid:<6}{mode:<8o}{size:<10}`\n",
|
|
664
|
+
mtime = 0,
|
|
665
|
+
uid = 0,
|
|
666
|
+
gid = 0,
|
|
667
|
+
mode = member.mode,
|
|
668
|
+
size = member.data.len()
|
|
669
|
+
);
|
|
670
|
+
debug_assert_eq!(header.len(), 60);
|
|
671
|
+
writer.write_all(header.as_bytes())?;
|
|
672
|
+
writer.write_all(&member.data)?;
|
|
673
|
+
if member.data.len() % 2 == 1 {
|
|
674
|
+
writer.write_all(b"\n")?;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
Ok(())
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
fn directory_size(path: &Path) -> Result<u64> {
|
|
681
|
+
let metadata =
|
|
682
|
+
fs::symlink_metadata(path).with_context(|| format!("Could not stat {}", path.display()))?;
|
|
683
|
+
if metadata.is_file() {
|
|
684
|
+
return Ok(metadata.len());
|
|
685
|
+
}
|
|
686
|
+
if !metadata.is_dir() {
|
|
687
|
+
return Ok(0);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
let mut size = 0;
|
|
691
|
+
for entry in fs::read_dir(path).with_context(|| format!("Could not read {}", path.display()))? {
|
|
692
|
+
let entry = entry?;
|
|
693
|
+
size += directory_size(&entry.path())?;
|
|
694
|
+
}
|
|
695
|
+
Ok(size)
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
fn debian_package_name(name: &str) -> String {
|
|
699
|
+
package_name(name)
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
fn rpm_package_name(name: &str) -> String {
|
|
703
|
+
package_name(name)
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
fn package_name(name: &str) -> String {
|
|
707
|
+
let mut package = name
|
|
708
|
+
.to_ascii_lowercase()
|
|
709
|
+
.chars()
|
|
710
|
+
.map(|char| {
|
|
711
|
+
if char.is_ascii_alphanumeric() || matches!(char, '+' | '-' | '.') {
|
|
712
|
+
char
|
|
713
|
+
} else {
|
|
714
|
+
'-'
|
|
715
|
+
}
|
|
716
|
+
})
|
|
717
|
+
.collect::<String>()
|
|
718
|
+
.trim_matches(['+', '-', '.'])
|
|
719
|
+
.to_string();
|
|
720
|
+
|
|
721
|
+
if package.len() < 2 {
|
|
722
|
+
package.push_str("app");
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
package
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
fn debian_version(version: Option<&str>) -> String {
|
|
729
|
+
let version = version.unwrap_or("0.1.0");
|
|
730
|
+
let sanitized = version
|
|
731
|
+
.chars()
|
|
732
|
+
.map(|char| {
|
|
733
|
+
if char.is_ascii_alphanumeric() || matches!(char, '.' | '+' | '-' | ':' | '~') {
|
|
734
|
+
char
|
|
735
|
+
} else {
|
|
736
|
+
'~'
|
|
737
|
+
}
|
|
738
|
+
})
|
|
739
|
+
.collect::<String>()
|
|
740
|
+
.trim_matches(['-', '~'])
|
|
741
|
+
.to_string();
|
|
742
|
+
|
|
743
|
+
if sanitized.is_empty() {
|
|
744
|
+
"0.1.0".to_string()
|
|
745
|
+
} else {
|
|
746
|
+
sanitized
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
fn rpm_version(version: Option<&str>) -> String {
|
|
751
|
+
let version = version.unwrap_or("0.1.0");
|
|
752
|
+
let sanitized = version
|
|
753
|
+
.chars()
|
|
754
|
+
.map(|char| {
|
|
755
|
+
if char.is_ascii_alphanumeric() || matches!(char, '.' | '+' | '_' | '~') {
|
|
756
|
+
char
|
|
757
|
+
} else {
|
|
758
|
+
'_'
|
|
759
|
+
}
|
|
760
|
+
})
|
|
761
|
+
.collect::<String>()
|
|
762
|
+
.trim_matches(['_', '~'])
|
|
763
|
+
.to_string();
|
|
764
|
+
|
|
765
|
+
if sanitized.is_empty() {
|
|
766
|
+
"0.1.0".to_string()
|
|
767
|
+
} else {
|
|
768
|
+
sanitized
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
fn debian_arch(arch: &str) -> String {
|
|
773
|
+
match arch {
|
|
774
|
+
"x64" => "amd64".to_string(),
|
|
775
|
+
"ia32" => "i386".to_string(),
|
|
776
|
+
"armv7l" => "armhf".to_string(),
|
|
777
|
+
arch => arch.to_string(),
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
fn rpm_arch(arch: &str) -> String {
|
|
782
|
+
match arch {
|
|
783
|
+
"x64" => "x86_64".to_string(),
|
|
784
|
+
"arm64" => "aarch64".to_string(),
|
|
785
|
+
"ia32" => "i386".to_string(),
|
|
786
|
+
"armv7l" => "armv7hl".to_string(),
|
|
787
|
+
arch => arch.to_string(),
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
fn single_line(value: &str) -> String {
|
|
792
|
+
value
|
|
793
|
+
.chars()
|
|
794
|
+
.map(|char| {
|
|
795
|
+
if char == '\n' || char == '\r' {
|
|
796
|
+
' '
|
|
797
|
+
} else {
|
|
798
|
+
char
|
|
799
|
+
}
|
|
800
|
+
})
|
|
801
|
+
.collect()
|
|
802
|
+
}
|
|
803
|
+
|
|
266
804
|
#[cfg(unix)]
|
|
267
805
|
fn unix_mode(metadata: &fs::Metadata, _fallback: u32) -> u32 {
|
|
268
806
|
use std::os::unix::fs::PermissionsExt;
|
|
@@ -324,6 +862,7 @@ impl MakeReport {
|
|
|
324
862
|
#[cfg(test)]
|
|
325
863
|
mod tests {
|
|
326
864
|
use super::*;
|
|
865
|
+
use std::io::Read;
|
|
327
866
|
use zip::ZipArchive;
|
|
328
867
|
|
|
329
868
|
#[test]
|
|
@@ -363,6 +902,74 @@ mod tests {
|
|
|
363
902
|
let _ = fs::remove_dir_all(root);
|
|
364
903
|
}
|
|
365
904
|
|
|
905
|
+
#[test]
|
|
906
|
+
fn builds_make_report_for_deb_target() {
|
|
907
|
+
let root = unique_temp_dir("deb-plan");
|
|
908
|
+
write_package_json(&root);
|
|
909
|
+
write_app_file(&root);
|
|
910
|
+
write_fake_electron_dist(&root);
|
|
911
|
+
|
|
912
|
+
let args = MakeArgs {
|
|
913
|
+
cwd: root.clone(),
|
|
914
|
+
out_dir: PathBuf::from("out"),
|
|
915
|
+
name: None,
|
|
916
|
+
platform: Some("linux".to_string()),
|
|
917
|
+
arch: Some("x64".to_string()),
|
|
918
|
+
target: crate::cli::MakeTarget::Deb,
|
|
919
|
+
skip_package: false,
|
|
920
|
+
force: false,
|
|
921
|
+
dry_run: true,
|
|
922
|
+
json: true,
|
|
923
|
+
};
|
|
924
|
+
let report = build_report(&args).expect("report should build");
|
|
925
|
+
|
|
926
|
+
assert_eq!(report.target, "deb");
|
|
927
|
+
assert!(Path::new(report.artifact.as_str()).ends_with(
|
|
928
|
+
PathBuf::from("out")
|
|
929
|
+
.join("make")
|
|
930
|
+
.join("deb")
|
|
931
|
+
.join("linux")
|
|
932
|
+
.join("x64")
|
|
933
|
+
.join("starter-app_0.1.0_amd64.deb")
|
|
934
|
+
));
|
|
935
|
+
|
|
936
|
+
let _ = fs::remove_dir_all(root);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
#[test]
|
|
940
|
+
fn builds_make_report_for_rpm_target() {
|
|
941
|
+
let root = unique_temp_dir("rpm-plan");
|
|
942
|
+
write_package_json(&root);
|
|
943
|
+
write_app_file(&root);
|
|
944
|
+
write_fake_electron_dist(&root);
|
|
945
|
+
|
|
946
|
+
let args = MakeArgs {
|
|
947
|
+
cwd: root.clone(),
|
|
948
|
+
out_dir: PathBuf::from("out"),
|
|
949
|
+
name: None,
|
|
950
|
+
platform: Some("linux".to_string()),
|
|
951
|
+
arch: Some("x64".to_string()),
|
|
952
|
+
target: crate::cli::MakeTarget::Rpm,
|
|
953
|
+
skip_package: false,
|
|
954
|
+
force: false,
|
|
955
|
+
dry_run: true,
|
|
956
|
+
json: true,
|
|
957
|
+
};
|
|
958
|
+
let report = build_report(&args).expect("report should build");
|
|
959
|
+
|
|
960
|
+
assert_eq!(report.target, "rpm");
|
|
961
|
+
assert!(Path::new(report.artifact.as_str()).ends_with(
|
|
962
|
+
PathBuf::from("out")
|
|
963
|
+
.join("make")
|
|
964
|
+
.join("rpm")
|
|
965
|
+
.join("linux")
|
|
966
|
+
.join("x64")
|
|
967
|
+
.join("starter-app-0.1.0-1.x86_64.rpm")
|
|
968
|
+
));
|
|
969
|
+
|
|
970
|
+
let _ = fs::remove_dir_all(root);
|
|
971
|
+
}
|
|
972
|
+
|
|
366
973
|
#[test]
|
|
367
974
|
fn makes_zip_artifact_after_packaging() {
|
|
368
975
|
let root = unique_temp_dir("execute");
|
|
@@ -405,10 +1012,192 @@ mod tests {
|
|
|
405
1012
|
let _ = fs::remove_dir_all(root);
|
|
406
1013
|
}
|
|
407
1014
|
|
|
1015
|
+
#[test]
|
|
1016
|
+
fn writes_deb_archive_with_control_and_data_members() {
|
|
1017
|
+
let root = unique_temp_dir("deb-archive");
|
|
1018
|
+
write_package_json(&root);
|
|
1019
|
+
write_app_file(&root);
|
|
1020
|
+
write_fake_electron_dist(&root);
|
|
1021
|
+
|
|
1022
|
+
let args = MakeArgs {
|
|
1023
|
+
cwd: root.clone(),
|
|
1024
|
+
out_dir: PathBuf::from("out"),
|
|
1025
|
+
name: None,
|
|
1026
|
+
platform: Some("linux".to_string()),
|
|
1027
|
+
arch: Some("x64".to_string()),
|
|
1028
|
+
target: crate::cli::MakeTarget::Deb,
|
|
1029
|
+
skip_package: false,
|
|
1030
|
+
force: false,
|
|
1031
|
+
dry_run: true,
|
|
1032
|
+
json: true,
|
|
1033
|
+
};
|
|
1034
|
+
let report = build_report(&args).expect("report should build");
|
|
1035
|
+
let bundle_dir = Path::new(report.package.bundle_dir().as_str());
|
|
1036
|
+
fs::create_dir_all(bundle_dir.join("resources/app"))
|
|
1037
|
+
.expect("fake bundle resources should be created");
|
|
1038
|
+
fs::write(bundle_dir.join("starter-app"), "").expect("fake binary should be written");
|
|
1039
|
+
fs::write(bundle_dir.join("resources/app/package.json"), "{}")
|
|
1040
|
+
.expect("fake app package should be written");
|
|
1041
|
+
|
|
1042
|
+
write_deb_archive(&report.package, Path::new(report.artifact.as_str()))
|
|
1043
|
+
.expect("deb should be written");
|
|
1044
|
+
|
|
1045
|
+
let members = read_ar_members(Path::new(report.artifact.as_str()));
|
|
1046
|
+
assert_eq!(
|
|
1047
|
+
members.get("debian-binary").map(Vec::as_slice),
|
|
1048
|
+
Some(&b"2.0\n"[..])
|
|
1049
|
+
);
|
|
1050
|
+
|
|
1051
|
+
let control = read_tar_file(
|
|
1052
|
+
members
|
|
1053
|
+
.get("control.tar.gz")
|
|
1054
|
+
.expect("control tar should exist"),
|
|
1055
|
+
"control",
|
|
1056
|
+
);
|
|
1057
|
+
assert!(control.contains("Package: starter-app"));
|
|
1058
|
+
assert!(control.contains("Architecture: amd64"));
|
|
1059
|
+
|
|
1060
|
+
let data = members.get("data.tar.gz").expect("data tar should exist");
|
|
1061
|
+
assert!(tar_contains(
|
|
1062
|
+
data,
|
|
1063
|
+
"opt/starter-app/resources/app/package.json"
|
|
1064
|
+
));
|
|
1065
|
+
assert!(tar_contains(
|
|
1066
|
+
data,
|
|
1067
|
+
"usr/share/applications/starter-app.desktop"
|
|
1068
|
+
));
|
|
1069
|
+
assert!(tar_contains(data, "usr/bin/starter-app"));
|
|
1070
|
+
|
|
1071
|
+
let _ = fs::remove_dir_all(root);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
#[test]
|
|
1075
|
+
fn writes_rpm_archive_with_metadata_and_payload_entries() {
|
|
1076
|
+
let root = unique_temp_dir("rpm-archive");
|
|
1077
|
+
write_package_json(&root);
|
|
1078
|
+
write_app_file(&root);
|
|
1079
|
+
write_fake_electron_dist(&root);
|
|
1080
|
+
|
|
1081
|
+
let args = MakeArgs {
|
|
1082
|
+
cwd: root.clone(),
|
|
1083
|
+
out_dir: PathBuf::from("out"),
|
|
1084
|
+
name: None,
|
|
1085
|
+
platform: Some("linux".to_string()),
|
|
1086
|
+
arch: Some("x64".to_string()),
|
|
1087
|
+
target: crate::cli::MakeTarget::Rpm,
|
|
1088
|
+
skip_package: false,
|
|
1089
|
+
force: false,
|
|
1090
|
+
dry_run: true,
|
|
1091
|
+
json: true,
|
|
1092
|
+
};
|
|
1093
|
+
let report = build_report(&args).expect("report should build");
|
|
1094
|
+
let bundle_dir = Path::new(report.package.bundle_dir().as_str());
|
|
1095
|
+
fs::create_dir_all(bundle_dir.join("resources/app"))
|
|
1096
|
+
.expect("fake bundle resources should be created");
|
|
1097
|
+
fs::write(bundle_dir.join("starter-app"), "").expect("fake binary should be written");
|
|
1098
|
+
fs::write(bundle_dir.join("resources/app/package.json"), "{}")
|
|
1099
|
+
.expect("fake app package should be written");
|
|
1100
|
+
|
|
1101
|
+
write_rpm_archive(&report.package, Path::new(report.artifact.as_str()))
|
|
1102
|
+
.expect("rpm should be written");
|
|
1103
|
+
|
|
1104
|
+
let rpm = rpm::Package::open(report.artifact.as_str()).expect("rpm should parse");
|
|
1105
|
+
assert_eq!(
|
|
1106
|
+
rpm.metadata.get_name().expect("name should read"),
|
|
1107
|
+
"starter-app"
|
|
1108
|
+
);
|
|
1109
|
+
assert_eq!(
|
|
1110
|
+
rpm.metadata.get_version().expect("version should read"),
|
|
1111
|
+
"0.1.0"
|
|
1112
|
+
);
|
|
1113
|
+
assert_eq!(
|
|
1114
|
+
rpm.metadata.get_release().expect("release should read"),
|
|
1115
|
+
"1"
|
|
1116
|
+
);
|
|
1117
|
+
assert_eq!(rpm.metadata.get_arch().expect("arch should read"), "x86_64");
|
|
1118
|
+
|
|
1119
|
+
let paths = rpm
|
|
1120
|
+
.metadata
|
|
1121
|
+
.get_file_paths()
|
|
1122
|
+
.expect("file paths should read")
|
|
1123
|
+
.into_iter()
|
|
1124
|
+
.map(|path| path.to_string_lossy().to_string())
|
|
1125
|
+
.collect::<Vec<_>>();
|
|
1126
|
+
assert!(paths.contains(&"/opt/starter-app/resources/app/package.json".to_string()));
|
|
1127
|
+
assert!(paths.contains(&"/usr/share/applications/starter-app.desktop".to_string()));
|
|
1128
|
+
assert!(paths.contains(&"/usr/bin/starter-app".to_string()));
|
|
1129
|
+
|
|
1130
|
+
let _ = fs::remove_dir_all(root);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
#[test]
|
|
1134
|
+
fn makes_deb_artifact_after_packaging_on_linux() {
|
|
1135
|
+
if !cfg!(target_os = "linux") {
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
let root = unique_temp_dir("deb-execute");
|
|
1140
|
+
write_package_json(&root);
|
|
1141
|
+
write_app_file(&root);
|
|
1142
|
+
write_fake_electron_dist(&root);
|
|
1143
|
+
|
|
1144
|
+
let args = MakeArgs {
|
|
1145
|
+
cwd: root.clone(),
|
|
1146
|
+
out_dir: PathBuf::from("out"),
|
|
1147
|
+
name: None,
|
|
1148
|
+
platform: None,
|
|
1149
|
+
arch: None,
|
|
1150
|
+
target: crate::cli::MakeTarget::Deb,
|
|
1151
|
+
skip_package: false,
|
|
1152
|
+
force: false,
|
|
1153
|
+
dry_run: false,
|
|
1154
|
+
json: false,
|
|
1155
|
+
};
|
|
1156
|
+
let mut report = build_report(&args).expect("report should build");
|
|
1157
|
+
|
|
1158
|
+
execute_make(&mut report, &args).expect("make should succeed");
|
|
1159
|
+
|
|
1160
|
+
assert!(Path::new(report.artifact.as_str()).exists());
|
|
1161
|
+
|
|
1162
|
+
let _ = fs::remove_dir_all(root);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
#[test]
|
|
1166
|
+
fn makes_rpm_artifact_after_packaging_on_linux() {
|
|
1167
|
+
if !cfg!(target_os = "linux") {
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
let root = unique_temp_dir("rpm-execute");
|
|
1172
|
+
write_package_json(&root);
|
|
1173
|
+
write_app_file(&root);
|
|
1174
|
+
write_fake_electron_dist(&root);
|
|
1175
|
+
|
|
1176
|
+
let args = MakeArgs {
|
|
1177
|
+
cwd: root.clone(),
|
|
1178
|
+
out_dir: PathBuf::from("out"),
|
|
1179
|
+
name: None,
|
|
1180
|
+
platform: None,
|
|
1181
|
+
arch: None,
|
|
1182
|
+
target: crate::cli::MakeTarget::Rpm,
|
|
1183
|
+
skip_package: false,
|
|
1184
|
+
force: false,
|
|
1185
|
+
dry_run: false,
|
|
1186
|
+
json: false,
|
|
1187
|
+
};
|
|
1188
|
+
let mut report = build_report(&args).expect("report should build");
|
|
1189
|
+
|
|
1190
|
+
execute_make(&mut report, &args).expect("make should succeed");
|
|
1191
|
+
|
|
1192
|
+
assert!(Path::new(report.artifact.as_str()).exists());
|
|
1193
|
+
|
|
1194
|
+
let _ = fs::remove_dir_all(root);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
408
1197
|
fn write_package_json(root: &Path) {
|
|
409
1198
|
fs::write(
|
|
410
1199
|
root.join("package.json"),
|
|
411
|
-
r#"{"name":"starter-app","version":"0.1.0","main":"src/main.js","devDependencies":{"electron":"30.0.0"}}"#,
|
|
1200
|
+
r#"{"name":"starter-app","version":"0.1.0","license":"MIT","main":"src/main.js","devDependencies":{"electron":"30.0.0"}}"#,
|
|
412
1201
|
)
|
|
413
1202
|
.expect("package.json should be written");
|
|
414
1203
|
}
|
|
@@ -446,4 +1235,71 @@ mod tests {
|
|
|
446
1235
|
fs::create_dir_all(&path).expect("temp dir should be created");
|
|
447
1236
|
path
|
|
448
1237
|
}
|
|
1238
|
+
|
|
1239
|
+
fn read_ar_members(path: &Path) -> std::collections::BTreeMap<String, Vec<u8>> {
|
|
1240
|
+
let bytes = fs::read(path).expect("ar archive should be readable");
|
|
1241
|
+
assert_eq!(&bytes[..8], b"!<arch>\n");
|
|
1242
|
+
|
|
1243
|
+
let mut members = std::collections::BTreeMap::new();
|
|
1244
|
+
let mut offset = 8;
|
|
1245
|
+
while offset < bytes.len() {
|
|
1246
|
+
let header = &bytes[offset..offset + 60];
|
|
1247
|
+
let name = std::str::from_utf8(&header[0..16])
|
|
1248
|
+
.expect("member name should be utf-8")
|
|
1249
|
+
.trim()
|
|
1250
|
+
.trim_end_matches('/')
|
|
1251
|
+
.to_string();
|
|
1252
|
+
let size = std::str::from_utf8(&header[48..58])
|
|
1253
|
+
.expect("member size should be utf-8")
|
|
1254
|
+
.trim()
|
|
1255
|
+
.parse::<usize>()
|
|
1256
|
+
.expect("member size should parse");
|
|
1257
|
+
let data_start = offset + 60;
|
|
1258
|
+
let data_end = data_start + size;
|
|
1259
|
+
members.insert(name, bytes[data_start..data_end].to_vec());
|
|
1260
|
+
offset = data_end + (size % 2);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
members
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
fn read_tar_file(archive: &[u8], path: &str) -> String {
|
|
1267
|
+
let decoder = flate2::read::GzDecoder::new(archive);
|
|
1268
|
+
let mut archive = tar::Archive::new(decoder);
|
|
1269
|
+
for entry in archive.entries().expect("tar entries should read") {
|
|
1270
|
+
let mut entry = entry.expect("tar entry should read");
|
|
1271
|
+
let entry_path = entry
|
|
1272
|
+
.path()
|
|
1273
|
+
.expect("tar path should read")
|
|
1274
|
+
.to_string_lossy()
|
|
1275
|
+
.trim_start_matches("./")
|
|
1276
|
+
.to_string();
|
|
1277
|
+
if entry_path == path {
|
|
1278
|
+
let mut contents = String::new();
|
|
1279
|
+
entry
|
|
1280
|
+
.read_to_string(&mut contents)
|
|
1281
|
+
.expect("tar file should read");
|
|
1282
|
+
return contents;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
panic!("tar file was not found: {path}");
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
fn tar_contains(archive: &[u8], path: &str) -> bool {
|
|
1290
|
+
let decoder = flate2::read::GzDecoder::new(archive);
|
|
1291
|
+
let mut archive = tar::Archive::new(decoder);
|
|
1292
|
+
archive
|
|
1293
|
+
.entries()
|
|
1294
|
+
.expect("tar entries should read")
|
|
1295
|
+
.any(|entry| {
|
|
1296
|
+
entry
|
|
1297
|
+
.expect("tar entry should read")
|
|
1298
|
+
.path()
|
|
1299
|
+
.expect("tar path should read")
|
|
1300
|
+
.to_string_lossy()
|
|
1301
|
+
.trim_start_matches("./")
|
|
1302
|
+
== path
|
|
1303
|
+
})
|
|
1304
|
+
}
|
|
449
1305
|
}
|