mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Revert 50103ee8 for now until I can come up with a better way to refactor the tests
This commit is contained in:
@@ -17,14 +17,80 @@ using SharpCompress.Compressors.Deflate;
|
||||
|
||||
namespace Squirrel
|
||||
{
|
||||
public class DeltaPackage : IEnableLogger
|
||||
public interface IDeltaPackageBuilder
|
||||
{
|
||||
ReleasePackage CreateDeltaPackage(ReleasePackage basePackage, ReleasePackage newPackage, string outputFile);
|
||||
ReleasePackage ApplyDeltaPackage(ReleasePackage basePackage, ReleasePackage deltaPackage, string outputFile);
|
||||
}
|
||||
|
||||
public class DeltaPackageBuilder : IEnableLogger, IDeltaPackageBuilder
|
||||
{
|
||||
readonly string localAppDirectory;
|
||||
public DeltaPackage(string localAppDataOverride = null)
|
||||
public DeltaPackageBuilder(string localAppDataOverride = null)
|
||||
{
|
||||
this.localAppDirectory = localAppDataOverride;
|
||||
}
|
||||
|
||||
public ReleasePackage CreateDeltaPackage(ReleasePackage basePackage, ReleasePackage newPackage, string outputFile)
|
||||
{
|
||||
Contract.Requires(basePackage != null);
|
||||
Contract.Requires(!String.IsNullOrEmpty(outputFile) && !File.Exists(outputFile));
|
||||
|
||||
if (basePackage.Version > newPackage.Version) {
|
||||
var message = String.Format(
|
||||
"You cannot create a delta package based on version {0} as it is a later version than {1}",
|
||||
basePackage.Version,
|
||||
newPackage.Version);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
if (basePackage.ReleasePackageFile == null) {
|
||||
throw new ArgumentException("The base package's release file is null", "basePackage");
|
||||
}
|
||||
|
||||
if (!File.Exists(basePackage.ReleasePackageFile)) {
|
||||
throw new FileNotFoundException("The base package release does not exist", basePackage.ReleasePackageFile);
|
||||
}
|
||||
|
||||
if (!File.Exists(newPackage.ReleasePackageFile)) {
|
||||
throw new FileNotFoundException("The new package release does not exist", newPackage.ReleasePackageFile);
|
||||
}
|
||||
|
||||
string baseTempPath = null;
|
||||
string tempPath = null;
|
||||
|
||||
using (Utility.WithTempDirectory(out baseTempPath, null))
|
||||
using (Utility.WithTempDirectory(out tempPath, null)) {
|
||||
var baseTempInfo = new DirectoryInfo(baseTempPath);
|
||||
var tempInfo = new DirectoryInfo(tempPath);
|
||||
|
||||
this.Log().Info("Extracting {0} and {1} into {2}",
|
||||
basePackage.ReleasePackageFile, newPackage.ReleasePackageFile, tempPath);
|
||||
|
||||
HelperExe.ExtractZipToDirectory(basePackage.ReleasePackageFile, baseTempInfo.FullName).Wait();
|
||||
HelperExe.ExtractZipToDirectory(newPackage.ReleasePackageFile, tempInfo.FullName).Wait();
|
||||
|
||||
// Collect a list of relative paths under 'lib' and map them
|
||||
// to their full name. We'll use this later to determine in
|
||||
// the new version of the package whether the file exists or
|
||||
// not.
|
||||
var baseLibFiles = baseTempInfo.GetAllFilesRecursively()
|
||||
.Where(x => x.FullName.ToLowerInvariant().Contains("lib" + Path.DirectorySeparatorChar))
|
||||
.ToDictionary(k => k.FullName.Replace(baseTempInfo.FullName, ""), v => v.FullName);
|
||||
|
||||
var newLibDir = tempInfo.GetDirectories().First(x => x.Name.ToLowerInvariant() == "lib");
|
||||
|
||||
foreach (var libFile in newLibDir.GetAllFilesRecursively()) {
|
||||
createDeltaForSingleFile(libFile, tempInfo, baseLibFiles);
|
||||
}
|
||||
|
||||
ReleasePackage.addDeltaFilesToContentTypes(tempInfo.FullName);
|
||||
HelperExe.CreateZipFromDirectory(outputFile, tempInfo.FullName).Wait();
|
||||
}
|
||||
|
||||
return new ReleasePackage(outputFile);
|
||||
}
|
||||
|
||||
public ReleasePackage ApplyDeltaPackage(ReleasePackage basePackage, ReleasePackage deltaPackage, string outputFile)
|
||||
{
|
||||
return ApplyDeltaPackage(basePackage, deltaPackage, outputFile, x => { });
|
||||
@@ -110,6 +176,76 @@ namespace Squirrel
|
||||
return new ReleasePackage(outputFile);
|
||||
}
|
||||
|
||||
void createDeltaForSingleFile(FileInfo targetFile, DirectoryInfo workingDirectory, Dictionary<string, string> baseFileListing)
|
||||
{
|
||||
// NB: There are three cases here that we'll handle:
|
||||
//
|
||||
// 1. Exists only in new => leave it alone, we'll use it directly.
|
||||
// 2. Exists in both old and new => write a dummy file so we know
|
||||
// to keep it.
|
||||
// 3. Exists in old but changed in new => create a delta file
|
||||
//
|
||||
// The fourth case of "Exists only in old => delete it in new"
|
||||
// is handled when we apply the delta package
|
||||
var relativePath = targetFile.FullName.Replace(workingDirectory.FullName, "");
|
||||
|
||||
if (!baseFileListing.ContainsKey(relativePath)) {
|
||||
this.Log().Info("{0} not found in base package, marking as new", relativePath);
|
||||
return;
|
||||
}
|
||||
|
||||
var oldData = File.ReadAllBytes(baseFileListing[relativePath]);
|
||||
var newData = File.ReadAllBytes(targetFile.FullName);
|
||||
|
||||
if (bytesAreIdentical(oldData, newData)) {
|
||||
this.Log().Info("{0} hasn't changed, writing dummy file", relativePath);
|
||||
|
||||
File.Create(targetFile.FullName + ".diff").Dispose();
|
||||
File.Create(targetFile.FullName + ".shasum").Dispose();
|
||||
targetFile.Delete();
|
||||
return;
|
||||
}
|
||||
|
||||
this.Log().Info("Delta patching {0} => {1}", baseFileListing[relativePath], targetFile.FullName);
|
||||
var msDelta = new MsDeltaCompression();
|
||||
|
||||
if (targetFile.Extension.Equals(".exe", StringComparison.OrdinalIgnoreCase) ||
|
||||
targetFile.Extension.Equals(".dll", StringComparison.OrdinalIgnoreCase) ||
|
||||
targetFile.Extension.Equals(".node", StringComparison.OrdinalIgnoreCase)) {
|
||||
try {
|
||||
msDelta.CreateDelta(baseFileListing[relativePath], targetFile.FullName, targetFile.FullName + ".diff");
|
||||
goto exit;
|
||||
} catch (Exception) {
|
||||
this.Log().Warn("We couldn't create a delta for {0}, attempting to create bsdiff", targetFile.Name);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
using (FileStream of = File.Create(targetFile.FullName + ".bsdiff")) {
|
||||
BinaryPatchUtility.Create(oldData, newData, of);
|
||||
|
||||
// NB: Create a dummy corrupt .diff file so that older
|
||||
// versions which don't understand bsdiff will fail out
|
||||
// until they get upgraded, instead of seeing the missing
|
||||
// file and just removing it.
|
||||
File.WriteAllText(targetFile.FullName + ".diff", "1");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
this.Log().WarnException(String.Format("We really couldn't create a delta for {0}", targetFile.Name), ex);
|
||||
|
||||
Utility.DeleteFileHarder(targetFile.FullName + ".bsdiff", true);
|
||||
Utility.DeleteFileHarder(targetFile.FullName + ".diff", true);
|
||||
return;
|
||||
}
|
||||
|
||||
exit:
|
||||
|
||||
var rl = ReleaseEntry.GenerateFromFile(new MemoryStream(newData), targetFile.Name + ".shasum");
|
||||
File.WriteAllText(targetFile.FullName + ".shasum", rl.EntryAsString, Encoding.UTF8);
|
||||
targetFile.Delete();
|
||||
}
|
||||
|
||||
|
||||
void applyDiffToFile(string deltaPath, string relativeFilePath, string workingDirectory)
|
||||
{
|
||||
var inputFile = Path.Combine(deltaPath, relativeFilePath);
|
||||
@@ -176,5 +312,23 @@ namespace Squirrel
|
||||
throw new ChecksumFailedException() { Filename = relativeFilePath };
|
||||
}
|
||||
}
|
||||
|
||||
bool bytesAreIdentical(byte[] oldData, byte[] newData)
|
||||
{
|
||||
if (oldData == null || newData == null) {
|
||||
return oldData == newData;
|
||||
}
|
||||
if (oldData.LongLength != newData.LongLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (long i = 0; i < newData.LongLength; i++) {
|
||||
if (oldData[i] != newData[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ using System.Threading.Tasks;
|
||||
using Squirrel;
|
||||
using Squirrel.SimpleSplat;
|
||||
|
||||
namespace SquirrelCli
|
||||
namespace Squirrel
|
||||
{
|
||||
internal static class HelperExe
|
||||
{
|
||||
@@ -23,6 +23,8 @@ namespace Squirrel
|
||||
string InputPackageFile { get; }
|
||||
string ReleasePackageFile { get; }
|
||||
string SuggestedReleaseFileName { get; }
|
||||
|
||||
string CreateReleasePackage(string outputFile, Func<string, string> releaseNotesProcessor = null, Action<string> contentsPostProcessHook = null);
|
||||
}
|
||||
|
||||
public class ReleasePackage : IEnableLogger, IReleasePackage
|
||||
@@ -48,6 +50,106 @@ namespace Squirrel
|
||||
|
||||
public SemanticVersion Version { get { return InputPackageFile.ToSemanticVersion(); } }
|
||||
|
||||
public string CreateReleasePackage(string outputFile, Func<string, string> releaseNotesProcessor = null, Action<string> contentsPostProcessHook = null)
|
||||
{
|
||||
Contract.Requires(!String.IsNullOrEmpty(outputFile));
|
||||
releaseNotesProcessor = releaseNotesProcessor ?? (x => (new Markdown()).Transform(x));
|
||||
|
||||
if (ReleasePackageFile != null) {
|
||||
return ReleasePackageFile;
|
||||
}
|
||||
|
||||
var package = new ZipPackage(InputPackageFile);
|
||||
|
||||
var dontcare = default(SemanticVersion);
|
||||
|
||||
// NB: Our test fixtures use packages that aren't SemVer compliant,
|
||||
// we don't really care that they aren't valid
|
||||
if (!ModeDetector.InUnitTestRunner() && !SemanticVersion.TryParseStrict(package.Version.ToString(), out dontcare)) {
|
||||
throw new Exception(
|
||||
String.Format(
|
||||
"Your package version is currently {0}, which is *not* SemVer-compatible, change this to be a SemVer version number",
|
||||
package.Version.ToString()));
|
||||
}
|
||||
|
||||
// we can tell from here what platform(s) the package targets
|
||||
// but given this is a simple package we only
|
||||
// ever expect one entry here (crash hard otherwise)
|
||||
var frameworks = package.GetSupportedFrameworks();
|
||||
if (frameworks.Count() > 1) {
|
||||
var platforms = frameworks
|
||||
.Aggregate(new StringBuilder(), (sb, f) => sb.Append(f.ToString() + "; "));
|
||||
|
||||
throw new InvalidOperationException(String.Format(
|
||||
"The input package file {0} targets multiple platforms - {1} - and cannot be transformed into a release package.", InputPackageFile, platforms));
|
||||
|
||||
} else if (!frameworks.Any()) {
|
||||
throw new InvalidOperationException(String.Format(
|
||||
"The input package file {0} targets no platform and cannot be transformed into a release package.", InputPackageFile));
|
||||
}
|
||||
|
||||
// CS - docs say we don't support dependencies. I can't think of any reason allowing this is useful.
|
||||
if (package.DependencySets.Any()) {
|
||||
throw new InvalidOperationException(String.Format(
|
||||
"The input package file {0} must have no dependencies.", InputPackageFile));
|
||||
}
|
||||
|
||||
var targetFramework = frameworks.Single();
|
||||
|
||||
this.Log().Info("Creating release package: {0} => {1}", InputPackageFile, outputFile);
|
||||
|
||||
string tempPath = null;
|
||||
|
||||
using (Utility.WithTempDirectory(out tempPath, null)) {
|
||||
var tempDir = new DirectoryInfo(tempPath);
|
||||
|
||||
extractZipWithEscaping(InputPackageFile, tempPath).Wait();
|
||||
|
||||
var specPath = tempDir.GetFiles("*.nuspec").First().FullName;
|
||||
|
||||
this.Log().Info("Removing unnecessary data");
|
||||
removeDependenciesFromPackageSpec(specPath);
|
||||
|
||||
if (releaseNotesProcessor != null) {
|
||||
renderReleaseNotesMarkdown(specPath, releaseNotesProcessor);
|
||||
}
|
||||
|
||||
addDeltaFilesToContentTypes(tempDir.FullName);
|
||||
|
||||
contentsPostProcessHook?.Invoke(tempPath);
|
||||
|
||||
HelperExe.CreateZipFromDirectory(outputFile, tempPath).Wait();
|
||||
|
||||
ReleasePackageFile = outputFile;
|
||||
return ReleasePackageFile;
|
||||
}
|
||||
}
|
||||
|
||||
static Task extractZipWithEscaping(string zipFilePath, string outFolder)
|
||||
{
|
||||
return Task.Run(() => {
|
||||
using (var za = ZipArchive.Open(zipFilePath))
|
||||
using (var reader = za.ExtractAllEntries()) {
|
||||
while (reader.MoveToNextEntry()) {
|
||||
var parts = reader.Entry.Key.Split('\\', '/').Select(x => Uri.UnescapeDataString(x));
|
||||
var decoded = String.Join(Path.DirectorySeparatorChar.ToString(), parts);
|
||||
|
||||
var fullTargetFile = Path.Combine(outFolder, decoded);
|
||||
var fullTargetDir = Path.GetDirectoryName(fullTargetFile);
|
||||
Directory.CreateDirectory(fullTargetDir);
|
||||
|
||||
Utility.Retry(() => {
|
||||
if (reader.Entry.IsDirectory) {
|
||||
Directory.CreateDirectory(Path.Combine(outFolder, decoded));
|
||||
} else {
|
||||
reader.WriteEntryToFile(Path.Combine(outFolder, decoded));
|
||||
}
|
||||
}, 5);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Task ExtractZipForInstall(string zipFilePath, string outFolder, string rootPackageFolder)
|
||||
{
|
||||
return ExtractZipForInstall(zipFilePath, outFolder, rootPackageFolder, x => { });
|
||||
@@ -109,6 +211,59 @@ namespace Squirrel
|
||||
progress(100);
|
||||
});
|
||||
}
|
||||
|
||||
void renderReleaseNotesMarkdown(string specPath, Func<string, string> releaseNotesProcessor)
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
doc.Load(specPath);
|
||||
|
||||
// XXX: This code looks full tart
|
||||
var metadata = doc.DocumentElement.ChildNodes
|
||||
.OfType<XmlElement>()
|
||||
.First(x => x.Name.ToLowerInvariant() == "metadata");
|
||||
|
||||
var releaseNotes = metadata.ChildNodes
|
||||
.OfType<XmlElement>()
|
||||
.FirstOrDefault(x => x.Name.ToLowerInvariant() == "releasenotes");
|
||||
|
||||
if (releaseNotes == null) {
|
||||
this.Log().Info("No release notes found in {0}", specPath);
|
||||
return;
|
||||
}
|
||||
|
||||
releaseNotes.InnerText = String.Format("<![CDATA[\n" + "{0}\n" + "]]>",
|
||||
releaseNotesProcessor(releaseNotes.InnerText));
|
||||
|
||||
doc.Save(specPath);
|
||||
}
|
||||
|
||||
void removeDependenciesFromPackageSpec(string specPath)
|
||||
{
|
||||
var xdoc = new XmlDocument();
|
||||
xdoc.Load(specPath);
|
||||
|
||||
var metadata = xdoc.DocumentElement.FirstChild;
|
||||
var dependenciesNode = metadata.ChildNodes.OfType<XmlElement>().FirstOrDefault(x => x.Name.ToLowerInvariant() == "dependencies");
|
||||
if (dependenciesNode != null) {
|
||||
metadata.RemoveChild(dependenciesNode);
|
||||
}
|
||||
|
||||
xdoc.Save(specPath);
|
||||
}
|
||||
|
||||
static internal void addDeltaFilesToContentTypes(string rootDirectory)
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
var path = Path.Combine(rootDirectory, "[Content_Types].xml");
|
||||
doc.Load(path);
|
||||
|
||||
ContentType.Merge(doc);
|
||||
ContentType.Clean(doc);
|
||||
|
||||
using (var sw = new StreamWriter(path, false, Encoding.UTF8)) {
|
||||
doc.Save(sw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ChecksumFailedException : Exception
|
||||
|
||||
@@ -358,7 +358,7 @@ namespace Squirrel
|
||||
var basePkg = new ReleasePackage(Path.Combine(rootAppDirectory, "packages", currentVersion.Filename));
|
||||
var deltaPkg = new ReleasePackage(Path.Combine(rootAppDirectory, "packages", releasesToApply.First().Filename));
|
||||
|
||||
var deltaBuilder = new DeltaPackage(Directory.GetParent(this.rootAppDirectory).FullName);
|
||||
var deltaBuilder = new DeltaPackageBuilder(Directory.GetParent(this.rootAppDirectory).FullName);
|
||||
|
||||
return deltaBuilder.ApplyDeltaPackage(basePkg, deltaPkg,
|
||||
Regex.Replace(deltaPkg.InputPackageFile, @"-delta.nupkg$", ".nupkg", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant),
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Squirrel;
|
||||
using Squirrel.Bsdiff;
|
||||
using Squirrel.SimpleSplat;
|
||||
|
||||
namespace SquirrelCli
|
||||
{
|
||||
internal class DeltaPackageBuilder : IEnableLogger
|
||||
{
|
||||
public ReleasePackage CreateDeltaPackage(ReleasePackage basePackage, ReleasePackage newPackage, string outputFile)
|
||||
{
|
||||
Contract.Requires(basePackage != null);
|
||||
Contract.Requires(!String.IsNullOrEmpty(outputFile) && !File.Exists(outputFile));
|
||||
|
||||
if (basePackage.Version > newPackage.Version) {
|
||||
var message = String.Format(
|
||||
"You cannot create a delta package based on version {0} as it is a later version than {1}",
|
||||
basePackage.Version,
|
||||
newPackage.Version);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
if (basePackage.ReleasePackageFile == null) {
|
||||
throw new ArgumentException("The base package's release file is null", "basePackage");
|
||||
}
|
||||
|
||||
if (!File.Exists(basePackage.ReleasePackageFile)) {
|
||||
throw new FileNotFoundException("The base package release does not exist", basePackage.ReleasePackageFile);
|
||||
}
|
||||
|
||||
if (!File.Exists(newPackage.ReleasePackageFile)) {
|
||||
throw new FileNotFoundException("The new package release does not exist", newPackage.ReleasePackageFile);
|
||||
}
|
||||
|
||||
string baseTempPath = null;
|
||||
string tempPath = null;
|
||||
|
||||
using (Utility.WithTempDirectory(out baseTempPath, null))
|
||||
using (Utility.WithTempDirectory(out tempPath, null)) {
|
||||
var baseTempInfo = new DirectoryInfo(baseTempPath);
|
||||
var tempInfo = new DirectoryInfo(tempPath);
|
||||
|
||||
this.Log().Info("Extracting {0} and {1} into {2}",
|
||||
basePackage.ReleasePackageFile, newPackage.ReleasePackageFile, tempPath);
|
||||
|
||||
HelperExe.ExtractZipToDirectory(basePackage.ReleasePackageFile, baseTempInfo.FullName).Wait();
|
||||
HelperExe.ExtractZipToDirectory(newPackage.ReleasePackageFile, tempInfo.FullName).Wait();
|
||||
|
||||
// Collect a list of relative paths under 'lib' and map them
|
||||
// to their full name. We'll use this later to determine in
|
||||
// the new version of the package whether the file exists or
|
||||
// not.
|
||||
var baseLibFiles = baseTempInfo.GetAllFilesRecursively()
|
||||
.Where(x => x.FullName.ToLowerInvariant().Contains("lib" + Path.DirectorySeparatorChar))
|
||||
.ToDictionary(k => k.FullName.Replace(baseTempInfo.FullName, ""), v => v.FullName);
|
||||
|
||||
var newLibDir = tempInfo.GetDirectories().First(x => x.Name.ToLowerInvariant() == "lib");
|
||||
|
||||
foreach (var libFile in newLibDir.GetAllFilesRecursively()) {
|
||||
createDeltaForSingleFile(libFile, tempInfo, baseLibFiles);
|
||||
}
|
||||
|
||||
ReleasePackageBuilder.addDeltaFilesToContentTypes(tempInfo.FullName);
|
||||
HelperExe.CreateZipFromDirectory(outputFile, tempInfo.FullName).Wait();
|
||||
}
|
||||
|
||||
return new ReleasePackage(outputFile);
|
||||
}
|
||||
|
||||
void createDeltaForSingleFile(FileInfo targetFile, DirectoryInfo workingDirectory, Dictionary<string, string> baseFileListing)
|
||||
{
|
||||
// NB: There are three cases here that we'll handle:
|
||||
//
|
||||
// 1. Exists only in new => leave it alone, we'll use it directly.
|
||||
// 2. Exists in both old and new => write a dummy file so we know
|
||||
// to keep it.
|
||||
// 3. Exists in old but changed in new => create a delta file
|
||||
//
|
||||
// The fourth case of "Exists only in old => delete it in new"
|
||||
// is handled when we apply the delta package
|
||||
var relativePath = targetFile.FullName.Replace(workingDirectory.FullName, "");
|
||||
|
||||
if (!baseFileListing.ContainsKey(relativePath)) {
|
||||
this.Log().Info("{0} not found in base package, marking as new", relativePath);
|
||||
return;
|
||||
}
|
||||
|
||||
var oldData = File.ReadAllBytes(baseFileListing[relativePath]);
|
||||
var newData = File.ReadAllBytes(targetFile.FullName);
|
||||
|
||||
if (bytesAreIdentical(oldData, newData)) {
|
||||
this.Log().Info("{0} hasn't changed, writing dummy file", relativePath);
|
||||
|
||||
File.Create(targetFile.FullName + ".diff").Dispose();
|
||||
File.Create(targetFile.FullName + ".shasum").Dispose();
|
||||
targetFile.Delete();
|
||||
return;
|
||||
}
|
||||
|
||||
this.Log().Info("Delta patching {0} => {1}", baseFileListing[relativePath], targetFile.FullName);
|
||||
var msDelta = new MsDeltaCompression();
|
||||
|
||||
if (targetFile.Extension.Equals(".exe", StringComparison.OrdinalIgnoreCase) ||
|
||||
targetFile.Extension.Equals(".dll", StringComparison.OrdinalIgnoreCase) ||
|
||||
targetFile.Extension.Equals(".node", StringComparison.OrdinalIgnoreCase)) {
|
||||
try {
|
||||
msDelta.CreateDelta(baseFileListing[relativePath], targetFile.FullName, targetFile.FullName + ".diff");
|
||||
goto exit;
|
||||
} catch (Exception) {
|
||||
this.Log().Warn("We couldn't create a delta for {0}, attempting to create bsdiff", targetFile.Name);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
using (FileStream of = File.Create(targetFile.FullName + ".bsdiff")) {
|
||||
BinaryPatchUtility.Create(oldData, newData, of);
|
||||
|
||||
// NB: Create a dummy corrupt .diff file so that older
|
||||
// versions which don't understand bsdiff will fail out
|
||||
// until they get upgraded, instead of seeing the missing
|
||||
// file and just removing it.
|
||||
File.WriteAllText(targetFile.FullName + ".diff", "1");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
this.Log().WarnException(String.Format("We really couldn't create a delta for {0}", targetFile.Name), ex);
|
||||
|
||||
Utility.DeleteFileHarder(targetFile.FullName + ".bsdiff", true);
|
||||
Utility.DeleteFileHarder(targetFile.FullName + ".diff", true);
|
||||
return;
|
||||
}
|
||||
|
||||
exit:
|
||||
|
||||
var rl = ReleaseEntry.GenerateFromFile(new MemoryStream(newData), targetFile.Name + ".shasum");
|
||||
File.WriteAllText(targetFile.FullName + ".shasum", rl.EntryAsString, Encoding.UTF8);
|
||||
targetFile.Delete();
|
||||
}
|
||||
|
||||
static bool bytesAreIdentical(byte[] oldData, byte[] newData)
|
||||
{
|
||||
if (oldData == null || newData == null) {
|
||||
return oldData == newData;
|
||||
}
|
||||
if (oldData.LongLength != newData.LongLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (long i = 0; i < newData.LongLength; i++) {
|
||||
if (oldData[i] != newData[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Squirrel;
|
||||
using Squirrel.Lib;
|
||||
|
||||
namespace SquirrelCli
|
||||
|
||||
@@ -157,7 +157,7 @@ namespace SquirrelCli
|
||||
foreach (var file in toProcess) {
|
||||
Log.Info("Creating release package: " + file.FullName);
|
||||
|
||||
var rp = new ReleasePackageBuilder(file.FullName);
|
||||
var rp = new ReleasePackage(file.FullName);
|
||||
rp.CreateReleasePackage(Path.Combine(di.FullName, rp.SuggestedReleaseFileName), contentsPostProcessHook: pkgPath => {
|
||||
|
||||
// create stub executable for all exe's in this package (except Squirrel!)
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Design;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
using Squirrel.MarkdownSharp;
|
||||
using Squirrel.NuGet;
|
||||
using Squirrel.SimpleSplat;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Archives.Zip;
|
||||
using SharpCompress.Readers;
|
||||
using Squirrel;
|
||||
|
||||
namespace SquirrelCli
|
||||
{
|
||||
internal class ReleasePackageBuilder : ReleasePackage
|
||||
{
|
||||
public ReleasePackageBuilder(string inputPackageFile, bool isReleasePackage = false) : base(inputPackageFile, isReleasePackage)
|
||||
{
|
||||
}
|
||||
|
||||
public string CreateReleasePackage(string outputFile, Func<string, string> releaseNotesProcessor = null, Action<string> contentsPostProcessHook = null)
|
||||
{
|
||||
Contract.Requires(!String.IsNullOrEmpty(outputFile));
|
||||
releaseNotesProcessor = releaseNotesProcessor ?? (x => (new Markdown()).Transform(x));
|
||||
|
||||
if (ReleasePackageFile != null) {
|
||||
return ReleasePackageFile;
|
||||
}
|
||||
|
||||
var package = new ZipPackage(InputPackageFile);
|
||||
|
||||
var dontcare = default(SemanticVersion);
|
||||
|
||||
// NB: Our test fixtures use packages that aren't SemVer compliant,
|
||||
// we don't really care that they aren't valid
|
||||
if (!ModeDetector.InUnitTestRunner() && !SemanticVersion.TryParseStrict(package.Version.ToString(), out dontcare)) {
|
||||
throw new Exception(
|
||||
String.Format(
|
||||
"Your package version is currently {0}, which is *not* SemVer-compatible, change this to be a SemVer version number",
|
||||
package.Version.ToString()));
|
||||
}
|
||||
|
||||
// we can tell from here what platform(s) the package targets
|
||||
// but given this is a simple package we only
|
||||
// ever expect one entry here (crash hard otherwise)
|
||||
var frameworks = package.GetSupportedFrameworks();
|
||||
if (frameworks.Count() > 1) {
|
||||
var platforms = frameworks
|
||||
.Aggregate(new StringBuilder(), (sb, f) => sb.Append(f.ToString() + "; "));
|
||||
|
||||
throw new InvalidOperationException(String.Format(
|
||||
"The input package file {0} targets multiple platforms - {1} - and cannot be transformed into a release package.", InputPackageFile, platforms));
|
||||
|
||||
} else if (!frameworks.Any()) {
|
||||
throw new InvalidOperationException(String.Format(
|
||||
"The input package file {0} targets no platform and cannot be transformed into a release package.", InputPackageFile));
|
||||
}
|
||||
|
||||
// CS - docs say we don't support dependencies. I can't think of any reason allowing this is useful.
|
||||
if (package.DependencySets.Any()) {
|
||||
throw new InvalidOperationException(String.Format(
|
||||
"The input package file {0} must have no dependencies.", InputPackageFile));
|
||||
}
|
||||
|
||||
var targetFramework = frameworks.Single();
|
||||
|
||||
this.Log().Info("Creating release package: {0} => {1}", InputPackageFile, outputFile);
|
||||
|
||||
string tempPath = null;
|
||||
|
||||
using (Utility.WithTempDirectory(out tempPath, null)) {
|
||||
var tempDir = new DirectoryInfo(tempPath);
|
||||
|
||||
extractZipWithEscaping(InputPackageFile, tempPath).Wait();
|
||||
|
||||
var specPath = tempDir.GetFiles("*.nuspec").First().FullName;
|
||||
|
||||
this.Log().Info("Removing unnecessary data");
|
||||
removeDependenciesFromPackageSpec(specPath);
|
||||
|
||||
if (releaseNotesProcessor != null) {
|
||||
renderReleaseNotesMarkdown(specPath, releaseNotesProcessor);
|
||||
}
|
||||
|
||||
addDeltaFilesToContentTypes(tempDir.FullName);
|
||||
|
||||
contentsPostProcessHook?.Invoke(tempPath);
|
||||
|
||||
HelperExe.CreateZipFromDirectory(outputFile, tempPath).Wait();
|
||||
|
||||
ReleasePackageFile = outputFile;
|
||||
return ReleasePackageFile;
|
||||
}
|
||||
}
|
||||
|
||||
static Task extractZipWithEscaping(string zipFilePath, string outFolder)
|
||||
{
|
||||
return Task.Run(() => {
|
||||
using (var za = ZipArchive.Open(zipFilePath))
|
||||
using (var reader = za.ExtractAllEntries()) {
|
||||
while (reader.MoveToNextEntry()) {
|
||||
var parts = reader.Entry.Key.Split('\\', '/').Select(x => Uri.UnescapeDataString(x));
|
||||
var decoded = String.Join(Path.DirectorySeparatorChar.ToString(), parts);
|
||||
|
||||
var fullTargetFile = Path.Combine(outFolder, decoded);
|
||||
var fullTargetDir = Path.GetDirectoryName(fullTargetFile);
|
||||
Directory.CreateDirectory(fullTargetDir);
|
||||
|
||||
Utility.Retry(() => {
|
||||
if (reader.Entry.IsDirectory) {
|
||||
Directory.CreateDirectory(Path.Combine(outFolder, decoded));
|
||||
} else {
|
||||
reader.WriteEntryToFile(Path.Combine(outFolder, decoded));
|
||||
}
|
||||
}, 5);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void renderReleaseNotesMarkdown(string specPath, Func<string, string> releaseNotesProcessor)
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
doc.Load(specPath);
|
||||
|
||||
// XXX: This code looks full tart
|
||||
var metadata = doc.DocumentElement.ChildNodes
|
||||
.OfType<XmlElement>()
|
||||
.First(x => x.Name.ToLowerInvariant() == "metadata");
|
||||
|
||||
var releaseNotes = metadata.ChildNodes
|
||||
.OfType<XmlElement>()
|
||||
.FirstOrDefault(x => x.Name.ToLowerInvariant() == "releasenotes");
|
||||
|
||||
if (releaseNotes == null) {
|
||||
this.Log().Info("No release notes found in {0}", specPath);
|
||||
return;
|
||||
}
|
||||
|
||||
releaseNotes.InnerText = String.Format("<![CDATA[\n" + "{0}\n" + "]]>",
|
||||
releaseNotesProcessor(releaseNotes.InnerText));
|
||||
|
||||
doc.Save(specPath);
|
||||
}
|
||||
|
||||
void removeDependenciesFromPackageSpec(string specPath)
|
||||
{
|
||||
var xdoc = new XmlDocument();
|
||||
xdoc.Load(specPath);
|
||||
|
||||
var metadata = xdoc.DocumentElement.FirstChild;
|
||||
var dependenciesNode = metadata.ChildNodes.OfType<XmlElement>().FirstOrDefault(x => x.Name.ToLowerInvariant() == "dependencies");
|
||||
if (dependenciesNode != null) {
|
||||
metadata.RemoveChild(dependenciesNode);
|
||||
}
|
||||
|
||||
xdoc.Save(specPath);
|
||||
}
|
||||
|
||||
static internal void addDeltaFilesToContentTypes(string rootDirectory)
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
var path = Path.Combine(rootDirectory, "[Content_Types].xml");
|
||||
doc.Load(path);
|
||||
|
||||
ContentType.Merge(doc);
|
||||
ContentType.Clean(doc);
|
||||
|
||||
using (var sw = new StreamWriter(path, false, Encoding.UTF8)) {
|
||||
doc.Save(sw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user