/// <summary> /// Generate a bundle, given the specification of embedded files /// </summary> /// <param name="fileSpecs"> /// An enumeration FileSpecs for the files to be embedded. /// /// Files in fileSpecs that are not bundled within the single file bundle, /// and should be published as separate files are marked as "IsExcluded" by this method. /// This doesn't include unbundled files that should be dropped, and not publised as output. /// </param> /// <returns> /// The full path the the generated bundle file /// </returns> /// <exceptions> /// ArgumentException if input is invalid /// IOExceptions and ArgumentExceptions from callees flow to the caller. /// </exceptions> public string GenerateBundle(IReadOnlyList <FileSpec> fileSpecs) { _tracer.Log($"Bundler Version: {BundlerMajorVersion}.{BundlerMinorVersion}"); _tracer.Log($"Bundle Version: {BundleManifest.BundleVersion}"); _tracer.Log($"Target Runtime: {_target}"); _tracer.Log($"Bundler Options: {_options}"); if (fileSpecs.Any(x => !x.IsValid())) { throw new ArgumentException("Invalid input specification: Found entry with empty source-path or bundle-relative-path."); } string hostSource; try { hostSource = fileSpecs.Where(x => x.BundleRelativePath.Equals(_hostName)).Single().SourcePath; } catch (InvalidOperationException) { throw new ArgumentException("Invalid input specification: Must specify the host binary"); } string bundlePath = Path.Combine(_outputDir, _hostName); if (File.Exists(bundlePath)) { _tracer.Log($"Ovewriting existing File {bundlePath}"); } BinaryUtils.CopyFile(hostSource, bundlePath); if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && HostModelUtils.IsCodesignAvailable()) { RemoveCodesignIfNecessary(bundlePath); } // Note: We're comparing file paths both on the OS we're running on as well as on the target OS for the app // We can't really make assumptions about the file systems (even on Linux there can be case insensitive file systems // and vice versa for Windows). So it's safer to do case sensitive comparison everywhere. var relativePathToSpec = new Dictionary <string, FileSpec>(StringComparer.Ordinal); long headerOffset = 0; using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(bundlePath))) { Stream bundle = writer.BaseStream; bundle.Position = bundle.Length; foreach (var fileSpec in fileSpecs) { string relativePath = fileSpec.BundleRelativePath; if (IsHost(relativePath)) { continue; } if (ShouldIgnore(relativePath)) { _tracer.Log($"Ignore: {relativePath}"); continue; } FileType type = InferType(fileSpec); if (ShouldExclude(type, relativePath)) { _tracer.Log($"Exclude [{type}]: {relativePath}"); fileSpec.Excluded = true; continue; } if (relativePathToSpec.TryGetValue(fileSpec.BundleRelativePath, out var existingFileSpec)) { if (!string.Equals(fileSpec.SourcePath, existingFileSpec.SourcePath, StringComparison.Ordinal)) { throw new ArgumentException($"Invalid input specification: Found entries '{fileSpec.SourcePath}' and '{existingFileSpec.SourcePath}' with the same BundleRelativePath '{fileSpec.BundleRelativePath}'"); } // Exact duplicate - intentionally skip and don't include a second copy in the bundle continue; } else { relativePathToSpec.Add(fileSpec.BundleRelativePath, fileSpec); } using (FileStream file = File.OpenRead(fileSpec.SourcePath)) { FileType targetType = _target.TargetSpecificFileType(type); (long startOffset, long compressedSize) = AddToBundle(bundle, file, targetType); FileEntry entry = BundleManifest.AddEntry(targetType, file, relativePath, startOffset, compressedSize, _target.BundleMajorVersion); _tracer.Log($"Embed: {entry}"); } } // Write the bundle manifest headerOffset = BundleManifest.Write(writer); _tracer.Log($"Header Offset={headerOffset}"); _tracer.Log($"Meta-data Size={writer.BaseStream.Position - headerOffset}"); _tracer.Log($"Bundle: Path={bundlePath}, Size={bundle.Length}"); } HostWriter.SetAsBundle(bundlePath, headerOffset); // Sign the bundle if requested if (_macosCodesign && RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && HostModelUtils.IsCodesignAvailable()) { var(exitCode, stdErr) = HostModelUtils.RunCodesign("-s -", bundlePath); if (exitCode != 0) { throw new InvalidOperationException($"Failed to codesign '{bundlePath}': {stdErr}"); } } return(bundlePath);