public void ItGeneratesExecutableImage() { using TestDirectory testDirectory = TestDirectory.Create(); string sourceAppHostMock = PrepareAppHostMockFile(testDirectory); string destinationFilePath = Path.Combine(testDirectory.Path, "DestinationAppHost.exe.mock"); string appBinaryFilePath = "Test/App/Binary/Path.dll"; // strip executable permissions from this AppHost template binary File.SetUnixFileMode(sourceAppHostMock, UnixFileMode.UserRead | UnixFileMode.GroupRead | UnixFileMode.OtherRead); // -rwxr-xr-x const UnixFileMode expectedPermissions = UnixFileMode.UserRead | UnixFileMode.UserExecute | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.GroupExecute | UnixFileMode.OtherRead | UnixFileMode.OtherExecute; HostWriter.CreateAppHost( sourceAppHostMock, destinationFilePath, appBinaryFilePath, windowsGraphicalUserInterface: true); // assert that the generated app has executable permissions // despite different permissions on the template binary. File.GetUnixFileMode(destinationFilePath) .Should() .Be(expectedPermissions); }
public void ItDoesNotCodeSignAppHostByDefault() { using (TestDirectory testDirectory = TestDirectory.Create()) { string sourceAppHostMock = PrepareAppHostMockFile(testDirectory); File.SetAttributes(sourceAppHostMock, FileAttributes.ReadOnly); string destinationFilePath = Path.Combine(testDirectory.Path, "DestinationAppHost.exe.mock"); string appBinaryFilePath = "Test/App/Binary/Path.dll"; HostWriter.CreateAppHost( sourceAppHostMock, destinationFilePath, appBinaryFilePath, windowsGraphicalUserInterface: false); const string codesign = @"/usr/bin/codesign"; var psi = new ProcessStartInfo() { Arguments = $"-d {destinationFilePath}", FileName = codesign, RedirectStandardError = true, }; using (var p = Process.Start(psi)) { p.Start(); p.StandardError.ReadToEnd() .Should().Contain($"{Path.GetFullPath(destinationFilePath)}: code object is not signed at all"); p.WaitForExit(); } } }
public void CanCodeSignAppHostOnMacOS(string subdir) { using (TestDirectory testDirectory = TestDirectory.Create(subdir)) { string sourceAppHostMock = PrepareAppHostMockFile(testDirectory); File.SetAttributes(sourceAppHostMock, FileAttributes.ReadOnly); string destinationFilePath = Path.Combine(testDirectory.Path, "DestinationAppHost.exe.mock"); string appBinaryFilePath = "Test/App/Binary/Path.dll"; HostWriter.CreateAppHost( sourceAppHostMock, destinationFilePath, appBinaryFilePath, windowsGraphicalUserInterface: false, enableMacOSCodeSign: true); const string codesign = @"/usr/bin/codesign"; var psi = new ProcessStartInfo() { Arguments = $"-d \"{destinationFilePath}\"", FileName = codesign, RedirectStandardError = true, }; using (var p = Process.Start(psi)) { p.Start(); p.StandardError.ReadToEnd() .Should().Contain($"Executable=/private{Path.GetFullPath(destinationFilePath)}"); p.WaitForExit(); // Successfully signed the apphost. Assert.True(p.ExitCode == 0, $"Expected exit code was '0' but '{codesign}' returned '{p.ExitCode}' instead."); } } }
public void ItGeneratesExecutableImage() { using (TestDirectory testDirectory = TestDirectory.Create()) { string sourceAppHostMock = PrepareAppHostMockFile(testDirectory); string destinationFilePath = Path.Combine(testDirectory.Path, "DestinationAppHost.exe.mock"); string appBinaryFilePath = "Test/App/Binary/Path.dll"; chmod(sourceAppHostMock, Convert.ToInt32("755", 8)) // match installed permissions: -rwxr-xr-x .Should() .NotBe(-1); GetLastError() .Should() .NotBe(4); // EINTR GetFilePermissionValue(sourceAppHostMock) .Should() .Be(Convert.ToInt32("755", 8)); HostWriter.CreateAppHost( sourceAppHostMock, destinationFilePath, appBinaryFilePath, windowsGraphicalUserInterface: true); GetFilePermissionValue(destinationFilePath) .Should() .Be(Convert.ToInt32("755", 8)); } int GetLastError() => Marshal.GetLastWin32Error(); }
public void ItEmbedsAppBinaryPath() { using (TestDirectory testDirectory = TestDirectory.Create()) { string sourceAppHostMock = PrepareAppHostMockFile(testDirectory); string destinationFilePath = Path.Combine(testDirectory.Path, "DestinationAppHost.exe.mock"); string appBinaryFilePath = "Test/App/Binary/Path.dll"; HostWriter.CreateAppHost( sourceAppHostMock, destinationFilePath, appBinaryFilePath); byte[] binaryPathBlob = Encoding.UTF8.GetBytes(appBinaryFilePath); byte[] result = File.ReadAllBytes(destinationFilePath); result .Skip(WindowsFileHeader.Length) .Take(binaryPathBlob.Length) .Should() .BeEquivalentTo(binaryPathBlob); BitConverter .ToUInt16(result, SubsystemOffset) .Should() .Be(3); } }
public void CodeSigningFailuresThrow() { using (TestDirectory testDirectory = TestDirectory.Create()) { string sourceAppHostMock = PrepareAppHostMockFile(testDirectory); File.SetAttributes(sourceAppHostMock, FileAttributes.ReadOnly); string destinationFilePath = Path.Combine(testDirectory.Path, "DestinationAppHost.exe.mock"); string appBinaryFilePath = "Test/App/Binary/Path.dll"; HostWriter.CreateAppHost( sourceAppHostMock, destinationFilePath, appBinaryFilePath, windowsGraphicalUserInterface: false, enableMacOSCodeSign: true); // Run CreateAppHost again to sign the apphost a second time, // causing codesign to fail. var exception = Assert.Throws <AppHostSigningException>(() => HostWriter.CreateAppHost( sourceAppHostMock, destinationFilePath, appBinaryFilePath, windowsGraphicalUserInterface: false, enableMacOSCodeSign: true)); Assert.Contains($"{destinationFilePath}: is already signed", exception.Message); } }
// This helper is used in lieu of SDK support for publishing apps using the singlefilehost. // It replaces the apphost with singlefilehost, and along with appropriate app.dll updates in the host. // For now, we leave behind the hostpolicy and hostfxr DLLs in the publish directory, because // removing them requires deps.json update. void ReplaceApphostWithStaticHost(TestProjectFixture fixture) { var staticHost = Path.Combine(fixture.RepoDirProvider.HostArtifacts, RuntimeInformationExtensions.GetExeFileNameForCurrentPlatform("singlefilehost")); HostWriter.CreateAppHost(staticHost, BundleHelper.GetHostPath(fixture), BundleHelper.GetAppPath(fixture)); }
/// <summary> /// Extract all files in the bundle to disk /// </summary> /// <exceptions> /// BundleException if the bundle is invalid or malformed. /// IOExceptions and ArgumentExceptions from callees flow to the caller. /// </exceptions> public void ExtractFiles() { try { trace.Log($"Bundler version {Bundler.Version}"); trace.Log($"Extract from file: {BundlePath}"); trace.Log($"Output Directory: {OutputDir}"); long headerOffset; if (!HostWriter.IsBundle(BundlePath, out headerOffset)) { throw new BundleException("Extraction failed: Bundle Signature not found."); } using (BinaryReader reader = new BinaryReader(File.OpenRead(BundlePath))) { Manifest manifest = Manifest.Read(reader, headerOffset); foreach (FileEntry entry in manifest.Files) { trace.Log($"Extract: {entry}"); string fileRelativePath = entry.RelativePath.Replace(FileEntry.DirectorySeparatorChar, Path.DirectorySeparatorChar); string filePath = Path.Combine(OutputDir, fileRelativePath); string fileDir = Path.GetDirectoryName(filePath); if ((fileDir != null) && !fileDir.Equals(String.Empty)) { Directory.CreateDirectory(fileDir); } reader.BaseStream.Position = entry.Offset; using (BinaryWriter file = new BinaryWriter(File.Create(filePath))) { long size = entry.Size; do { int copySize = (int)(size <= int.MaxValue ? size : int.MaxValue); file.Write(reader.ReadBytes(copySize)); size -= copySize; } while (size > 0); } } } } catch (EndOfStreamException) { // Trying to read non-existant bits in the bundle throw new BundleException("Malformed Bundle"); } catch (ArgumentOutOfRangeException) { // Trying to set file-stream position to an invalid value throw new BundleException("Malformed Bundle"); } }
public static string UseFrameworkDependentHost(TestProjectFixture testFixture) { var appHost = Path.Combine( testFixture.RepoDirProvider.HostArtifacts, RuntimeInformationExtensions.GetExeFileNameForCurrentPlatform("apphost")); var publishedHostPath = BundleHelper.GetHostPath(testFixture); HostWriter.CreateAppHost(appHost, publishedHostPath, BundleHelper.GetAppName(testFixture)); return(publishedHostPath); }
// This helper is used in lieu of SDK support for publishing apps using the singlefilehost. // It replaces the apphost with singlefilehost, and along with appropriate app.dll updates in the host. // For now, we leave behind the hostpolicy and hostfxr DLLs in the publish directory, because // removing them requires deps.json update. public static string UseSingleFileSelfContainedHost(TestProjectFixture testFixture) { var singleFileHost = Path.Combine( testFixture.RepoDirProvider.HostArtifacts, RuntimeInformationExtensions.GetExeFileNameForCurrentPlatform("singlefilehost")); var publishedHostPath = BundleHelper.GetHostPath(testFixture); HostWriter.CreateAppHost(singleFileHost, publishedHostPath, BundleHelper.GetAppName(testFixture)); return(publishedHostPath); }
public void CreateApphostShellShim(string entryPoint, string shimPath) { var appHostDestinationFilePath = Path.GetFullPath(shimPath); string entryPointFullPath = Path.GetFullPath(entryPoint); var appBinaryFilePath = Path.GetRelativePath(Path.GetDirectoryName(appHostDestinationFilePath), entryPointFullPath); // by passing null to assemblyToCopyResorcesFrom, it will skip copying resources, // which is only supported on Windows HostWriter.CreateAppHost(appHostSourceFilePath: Path.GetFullPath(_apphost), appHostDestinationFilePath: appHostDestinationFilePath, appBinaryFilePath: appBinaryFilePath, windowsGraphicalUserInterface: false, assemblyToCopyResorcesFrom: null); }
public void ItFailsToEmbedTooLongAppBinaryPath() { using (TestDirectory testDirectory = TestDirectory.Create()) { string sourceAppHostMock = PrepareAppHostMockFile(testDirectory); string destinationFilePath = Path.Combine(testDirectory.Path, "DestinationAppHost.exe.mock"); string appBinaryFilePath = new string('a', 1024 + 5); Assert.Throws <AppNameTooLongException>(() => HostWriter.CreateAppHost( sourceAppHostMock, destinationFilePath, appBinaryFilePath)); File.Exists(destinationFilePath).Should().BeFalse(); } }
public void CanCreateAppHost() { using (TestDirectory testDirectory = TestDirectory.Create()) { string sourceAppHostMock = PrepareAppHostMockFile(testDirectory); File.SetAttributes(sourceAppHostMock, FileAttributes.ReadOnly); string destinationFilePath = Path.Combine(testDirectory.Path, "DestinationAppHost.exe.mock"); string appBinaryFilePath = "Test/App/Binary/Path.dll"; HostWriter.CreateAppHost( sourceAppHostMock, destinationFilePath, appBinaryFilePath, windowsGraphicalUserInterface: false); File.SetAttributes(sourceAppHostMock, FileAttributes.Normal); } }
public void ItCanSetWindowsGUISubsystem() { using (TestDirectory testDirectory = TestDirectory.Create()) { string sourceAppHostMock = PrepareAppHostMockFile(testDirectory); string destinationFilePath = Path.Combine(testDirectory.Path, "DestinationAppHost.exe.mock"); string appBinaryFilePath = "Test/App/Binary/Path.dll"; HostWriter.CreateAppHost( sourceAppHostMock, destinationFilePath, appBinaryFilePath, windowsGraphicalUserInterface: true); BitConverter .ToUInt16(File.ReadAllBytes(destinationFilePath), SubsystemOffset) .Should() .Be(2); } }
public void CreateApphostShellShim(FilePath entryPoint, FilePath shimPath) { string appHostSourcePath; if (OperatingSystem.IsWindows()) { appHostSourcePath = Path.Combine(_appHostSourceDirectory, ApphostNameWithoutExtension + ".exe"); } else { appHostSourcePath = Path.Combine(_appHostSourceDirectory, ApphostNameWithoutExtension); } var appHostDestinationFilePath = Path.GetFullPath(shimPath.Value); string entryPointFullPath = Path.GetFullPath(entryPoint.Value); var appBinaryFilePath = Path.GetRelativePath(Path.GetDirectoryName(appHostDestinationFilePath), entryPointFullPath); if (ResourceUpdater.IsSupportedOS()) { var windowsGraphicalUserInterfaceBit = PEUtils.GetWindowsGraphicalUserInterfaceBit(entryPointFullPath); HostWriter.CreateAppHost(appHostSourceFilePath: appHostSourcePath, appHostDestinationFilePath: appHostDestinationFilePath, appBinaryFilePath: appBinaryFilePath, windowsGraphicalUserInterface: (windowsGraphicalUserInterfaceBit == WindowsGUISubsystem), assemblyToCopyResorcesFrom: entryPointFullPath); } else { // by passing null to assemblyToCopyResorcesFrom, it will skip copying resources, // which is only supported on Windows HostWriter.CreateAppHost(appHostSourceFilePath: appHostSourcePath, appHostDestinationFilePath: appHostDestinationFilePath, appBinaryFilePath: appBinaryFilePath, windowsGraphicalUserInterface: false, assemblyToCopyResorcesFrom: null, enableMacOSCodeSign: OperatingSystem.IsMacOS()); } _filePermissionSetter.SetUserExecutionPermission(appHostDestinationFilePath); }
public void ItFailsToEmbedAppBinaryIfHashIsWrong() { using (TestDirectory testDirectory = TestDirectory.Create()) { string sourceAppHostMock = PrepareAppHostMockFile(testDirectory, content => { // Corrupt the hash value content[WindowsFileHeader.Length + 1]++; }); string destinationFilePath = Path.Combine(testDirectory.Path, "DestinationAppHost.exe.mock"); string appBinaryFilePath = "Test/App/Binary/Path.dll"; Assert.Throws <PlaceHolderNotFoundInAppHostException>(() => HostWriter.CreateAppHost( sourceAppHostMock, destinationFilePath, appBinaryFilePath)); File.Exists(destinationFilePath).Should().BeFalse(); } }
public void ItFailsToSetGUISubsystemWithWrongDefault() { using (TestDirectory testDirectory = TestDirectory.Create()) { string sourceAppHostMock = PrepareAppHostMockFile(testDirectory, content => { // Corrupt the value of the subsystem (the default should be 3) content[SubsystemOffset] = 42; }); string destinationFilePath = Path.Combine(testDirectory.Path, "DestinationAppHost.exe.mock"); string appBinaryFilePath = "Test/App/Binary/Path.dll"; Assert.Throws <AppHostNotCUIException>(() => HostWriter.CreateAppHost( sourceAppHostMock, destinationFilePath, appBinaryFilePath, windowsGraphicalUserInterface: true)); File.Exists(destinationFilePath).Should().BeFalse(); } }
public void ItFailsToSetGUISubsystemOnNonWindowsPEFile() { using (TestDirectory testDirectory = TestDirectory.Create()) { string sourceAppHostMock = PrepareAppHostMockFile(testDirectory, content => { // Windows PE files must start with 0x5A4D, so write some other value here. content[0] = 1; content[1] = 2; }); string destinationFilePath = Path.Combine(testDirectory.Path, "DestinationAppHost.exe.mock"); string appBinaryFilePath = "Test/App/Binary/Path.dll"; Assert.Throws <AppHostNotPEFileException>(() => HostWriter.CreateAppHost( sourceAppHostMock, destinationFilePath, appBinaryFilePath, windowsGraphicalUserInterface: true)); File.Exists(destinationFilePath).Should().BeFalse(); } }
protected override void ExecuteCore() { try { if (ResourceUpdater.IsSupportedOS()) { HostWriter.CreateAppHost(appHostSourceFilePath: AppHostSourcePath, appHostDestinationFilePath: AppHostDestinationPath, appBinaryFilePath: AppBinaryName, windowsGraphicalUserInterface: WindowsGraphicalUserInterface, assemblyToCopyResorcesFrom: IntermediateAssembly); } else { // by passing null to assemblyToCopyResorcesFrom, it will skip copying resorces, // which is only supported on Windows if (WindowsGraphicalUserInterface) { Log.LogWarning(Strings.AppHostCustomizationRequiresWindowsHostWarning); } HostWriter.CreateAppHost(appHostSourceFilePath: AppHostSourcePath, appHostDestinationFilePath: AppHostDestinationPath, appBinaryFilePath: AppBinaryName, windowsGraphicalUserInterface: false, assemblyToCopyResorcesFrom: null); } } catch (AppNameTooLongException ex) { throw new BuildErrorException(Strings.FileNameIsTooLong, ex.LongName); } catch (PlaceHolderNotFoundInAppHostException ex) { throw new BuildErrorException(Strings.AppHostHasBeenModified, AppHostSourcePath, BitConverter.ToString(ex.MissingPattern)); } }
protected override void ExecuteCore() { var embeddedApphostPaths = new List <ITaskItem>(); foreach (var runtimeIdentifier in ShimRuntimeIdentifiers.Select(r => r.ItemSpec)) { var resolvedApphostAssetPath = GetApphostAsset(ApphostsForShimRuntimeIdentifiers, runtimeIdentifier); var packagedShimOutputDirectoryAndRid = Path.Combine( PackagedShimOutputDirectory, runtimeIdentifier); var appHostDestinationFilePath = Path.Combine( packagedShimOutputDirectoryAndRid, ToolCommandName + ExecutableExtension.ForRuntimeIdentifier(runtimeIdentifier)); Directory.CreateDirectory(packagedShimOutputDirectoryAndRid); // per https://github.com/dotnet/cli/issues/9870 nuget layout (as in {packageid}/{packageversion}/tools/)is normalized version var normalizedPackageVersion = NuGetVersion.Parse(PackageVersion).ToNormalizedString(); // This is the embedded string. We should normalize it on forward slash, so the file won't be different according to // build machine. var appBinaryFilePath = string.Join("/", new[] { ".store", PackageId.ToLowerInvariant(), normalizedPackageVersion, PackageId.ToLowerInvariant(), normalizedPackageVersion, "tools", NuGetUtils.ParseFrameworkName(TargetFrameworkMoniker).GetShortFolderName(), "any", ToolEntryPoint }); try { var windowsGraphicalUserInterface = runtimeIdentifier.StartsWith("win") && "WinExe".Equals(OutputType, StringComparison.OrdinalIgnoreCase); if (ResourceUpdater.IsSupportedOS() && runtimeIdentifier.StartsWith("win")) { HostWriter.CreateAppHost(appHostSourceFilePath: resolvedApphostAssetPath, appHostDestinationFilePath: appHostDestinationFilePath, appBinaryFilePath: appBinaryFilePath, windowsGraphicalUserInterface: windowsGraphicalUserInterface, assemblyToCopyResorcesFrom: IntermediateAssembly); } else { // by passing null to assemblyToCopyResorcesFrom, it will skip copying resources, // which is only supported on Windows if (windowsGraphicalUserInterface) { Log.LogWarning(Strings.AppHostCustomizationRequiresWindowsHostWarning); } HostWriter.CreateAppHost(appHostSourceFilePath: resolvedApphostAssetPath, appHostDestinationFilePath: appHostDestinationFilePath, appBinaryFilePath: appBinaryFilePath, windowsGraphicalUserInterface: false, assemblyToCopyResorcesFrom: null); } } catch (AppNameTooLongException ex) { throw new BuildErrorException(Strings.FileNameIsTooLong, ex.LongName); } catch (PlaceHolderNotFoundInAppHostException ex) { throw new BuildErrorException(Strings.AppHostHasBeenModified, resolvedApphostAssetPath, BitConverter.ToString(ex.MissingPattern)); } var item = new TaskItem(appHostDestinationFilePath); item.SetMetadata(MetadataKeys.ShimRuntimeIdentifier, runtimeIdentifier); embeddedApphostPaths.Add(item); } EmbeddedApphostPaths = embeddedApphostPaths.ToArray(); }
protected override void ExecuteCore() { try { var isGUI = WindowsGraphicalUserInterface; var resourcesAssembly = IntermediateAssembly; if (!ResourceUpdater.IsSupportedOS()) { if (isGUI) { Log.LogWarning(Strings.AppHostCustomizationRequiresWindowsHostWarning); } isGUI = false; resourcesAssembly = null; } int attempts = 0; while (true) { try { HostWriter.CreateAppHost(appHostSourceFilePath: AppHostSourcePath, appHostDestinationFilePath: AppHostDestinationPath, appBinaryFilePath: AppBinaryName, windowsGraphicalUserInterface: isGUI, assemblyToCopyResorcesFrom: resourcesAssembly); return; } catch (Exception ex) when(ex is IOException || ex is UnauthorizedAccessException || ex is HResultException || (ex is AggregateException && (ex.InnerException is IOException || ex.InnerException is UnauthorizedAccessException))) { if (Retries < 0 || attempts == Retries) { throw; } ++attempts; string message = ex.Message; if (ex is AggregateException) { message = ex.InnerException.Message; } Log.LogWarning( string.Format(Strings.AppHostCreationFailedWithRetry, attempts, Retries + 1, message)); if (RetryDelayMilliseconds > 0) { Thread.Sleep(RetryDelayMilliseconds); } } } } catch (AppNameTooLongException ex) { throw new BuildErrorException(Strings.FileNameIsTooLong, ex.LongName); } catch (PlaceHolderNotFoundInAppHostException ex) { throw new BuildErrorException(Strings.AppHostHasBeenModified, AppHostSourcePath, BitConverter.ToString(ex.MissingPattern)); } }
/// <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); }
/// <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); }
/// <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); }
public static void Main(string[] args) { try { ParseArgs(args); } catch (ArgumentException e) { Fail("ERROR", e.Message); Usage(); return; } if (NeedHelp) { Usage(); return; } if (CreateHost) { HostWriter.CreateAppHost(Template, Path.Combine(SourceDir, Host), App); } if (!CreateBundle) { return; } Bundler bundler = new Bundler(Host, OutputDir, Options, OS, Framework, Diagnostics); // Get all files in the source directory and all sub-directories. string[] sources = Directory.GetFiles(SourceDir, searchPattern: "*", searchOption: SearchOption.AllDirectories); // Sort the file names to keep the bundle construction deterministic. Array.Sort(sources, StringComparer.Ordinal); List <FileSpec> fileSpecs = new List <FileSpec>(sources.Length); foreach (var file in sources) { fileSpecs.Add(new FileSpec(file, Path.GetRelativePath(SourceDir, file))); } bundler.GenerateBundle(fileSpecs); if (!CopyExcluded) { return; } foreach (var spec in fileSpecs) { if (spec.Excluded) { var outputPath = Path.Combine(OutputDir, spec.BundleRelativePath); Directory.CreateDirectory(Path.GetDirectoryName(outputPath)); File.Copy(spec.SourcePath, outputPath, true); } } }
public void SetUp() { theWriter = new HostWriter(new TypeDescriptorCache()); }
/// <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); }