예제 #1
0
        /// <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);

            // 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);

            return(bundlePath);
        }
예제 #2
0
        /// <summary>
        /// Generate a bundle, given the specification of embedded files
        /// </summary>
        /// <param name="fileSpecs">
        /// An enumeration FileSpecs for the files to be embedded.
        /// </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)
        {
            trace.Log($"Bundler version {Version}");

            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");
            }

            if (fileSpecs.GroupBy(file => file.BundleRelativePath).Where(g => g.Count() > 1).Any())
            {
                throw new ArgumentException("Invalid input specification: Found multiple entries with the same BundleRelativePath");
            }

            string bundlePath = Path.Combine(OutputDir, HostName);

            if (File.Exists(bundlePath))
            {
                trace.Log($"Ovewriting existing File {bundlePath}");
            }

            BinaryUtils.CopyFile(hostSource, bundlePath);

            long headerOffset = 0;

            using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(bundlePath)))
            {
                Stream bundle = writer.BaseStream;
                bundle.Position = bundle.Length;

                // Write the files from the specification into the bundle
                // Alignment:
                //   Assemblies are written aligned at "AssemblyAlignment" bytes to facilitate loading from bundle
                //   Remaining files (native binaries and other files) are written without alignment.
                //
                // The unaligned files are written first, followed by the aligned files,
                // and finally the bundle manifest.
                // TODO: Order file writes to minimize file size.

                List <Tuple <FileSpec, FileType> > ailgnedFiles = new List <Tuple <FileSpec, FileType> >();
                bool NeedsAlignment(FileType type) => type == FileType.IL || type == FileType.Ready2Run;

                foreach (var fileSpec in fileSpecs)
                {
                    if (!ShouldEmbed(fileSpec.BundleRelativePath))
                    {
                        trace.Log($"Skip: {fileSpec.BundleRelativePath}");
                        continue;
                    }

                    using (FileStream file = File.OpenRead(fileSpec.SourcePath))
                    {
                        FileType type = InferType(fileSpec.BundleRelativePath, file);

                        if (NeedsAlignment(type))
                        {
                            ailgnedFiles.Add(Tuple.Create(fileSpec, type));
                            continue;
                        }

                        long      startOffset = AddToBundle(bundle, file, shouldAlign: false);
                        FileEntry entry       = BundleManifest.AddEntry(type, fileSpec.BundleRelativePath, startOffset, file.Length);
                        trace.Log($"Embed: {entry}");
                    }
                }

                foreach (var tuple in ailgnedFiles)
                {
                    var fileSpec = tuple.Item1;
                    var type     = tuple.Item2;

                    using (FileStream file = File.OpenRead(fileSpec.SourcePath))
                    {
                        long      startOffset = AddToBundle(bundle, file, shouldAlign: true);
                        FileEntry entry       = BundleManifest.AddEntry(type, fileSpec.BundleRelativePath, startOffset, file.Length);
                        trace.Log($"Embed: {entry}");
                    }
                }

                // Write the bundle manifest
                headerOffset = BundleManifest.Write(writer);
                trace.Log($"Header Offset={headerOffset}");
                trace.Log($"Meta-data Size={writer.BaseStream.Position - headerOffset}");
                trace.Log($"Bundle: Path={bundlePath}, Size={bundle.Length}");
            }

            HostWriter.SetAsBundle(bundlePath, headerOffset);

            return(bundlePath);
        }
예제 #3
0
        /// <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: {Manifest.CurrentVersion}");
            Tracer.Log($"Bundler Header: {BundleManifest.DesiredVersion}");
            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");
            }

            if (fileSpecs.GroupBy(file => file.BundleRelativePath).Where(g => g.Count() > 1).Any())
            {
                throw new ArgumentException("Invalid input specification: Found multiple entries with the same BundleRelativePath");
            }

            string bundlePath = Path.Combine(OutputDir, HostName);

            if (File.Exists(bundlePath))
            {
                Tracer.Log($"Ovewriting existing File {bundlePath}");
            }

            BinaryUtils.CopyFile(hostSource, bundlePath);

            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;
                    }

                    using (FileStream file = File.OpenRead(fileSpec.SourcePath))
                    {
                        FileType  targetType  = Target.TargetSpecificFileType(type);
                        long      startOffset = AddToBundle(bundle, file, targetType);
                        FileEntry entry       = BundleManifest.AddEntry(targetType, relativePath, startOffset, file.Length);
                        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);

            return(bundlePath);
        }
예제 #4
0
        /// <summary>
        /// Generate a bundle, given the specification of embedded files
        /// </summary>
        /// <param name="fileSpecs">
        /// An enumeration FileSpecs for the files to be embedded.
        /// </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)
        {
            trace.Log($"Bundler version {Version}");

            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");
            }

            if (fileSpecs.GroupBy(file => file.BundleRelativePath).Where(g => g.Count() > 1).Any())
            {
                throw new ArgumentException("Invalid input specification: Found multiple entries with the same BundleRelativePath");
            }

            string bundlePath = Path.Combine(OutputDir, HostName);

            if (File.Exists(bundlePath))
            {
                trace.Log($"Ovewriting existing File {bundlePath}");
            }

            BinaryUtils.CopyFile(hostSource, bundlePath);

            long headerOffset = 0;

            using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(bundlePath)))
            {
                Stream bundle = writer.BaseStream;
                bundle.Position = bundle.Length;

                // Write the files from the specification into the bundle
                foreach (var fileSpec in fileSpecs)
                {
                    if (!ShouldEmbed(fileSpec.BundleRelativePath))
                    {
                        trace.Log($"Skip: {fileSpec.BundleRelativePath}");
                        continue;
                    }

                    using (FileStream file = File.OpenRead(fileSpec.SourcePath))
                    {
                        FileType  type        = InferType(fileSpec.BundleRelativePath, file);
                        long      startOffset = AddToBundle(bundle, file, type);
                        FileEntry entry       = BundleManifest.AddEntry(type, fileSpec.BundleRelativePath, startOffset, file.Length);
                        trace.Log($"Embed: {entry}");
                    }
                }

                // Write the bundle manifest
                headerOffset = BundleManifest.Write(writer);
                trace.Log($"Header Offset={headerOffset}");
                trace.Log($"Meta-data Size={writer.BaseStream.Position - headerOffset}");
                trace.Log($"Bundle: Path={bundlePath}, Size={bundle.Length}");
            }

            HostWriter.SetAsBundle(bundlePath, headerOffset);

            return(bundlePath);
        }