Skip to content

Commit

Permalink
Implemented handling of <copy-from> recipe step
Browse files Browse the repository at this point in the history
  • Loading branch information
bastianeicher committed May 3, 2017
1 parent 132cd2f commit 928485b
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 2 deletions.
4 changes: 4 additions & 0 deletions src/Backend/Services.Interfaces/Fetchers/FetcherBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ private void Cook([NotNull] Recipe recipe, ManifestDigest manifestDigest)
foreach (var archive in recipe.Steps.OfType<Archive>())
ArchiveExtractor.VerifySupport(archive.MimeType);

// Download any other Implementations required by the Recipe
foreach (var copyFromStep in recipe.Steps.OfType<CopyFromStep>())
Fetch(new[] {copyFromStep.Implementation});

var downloadedFiles = new List<TemporaryFile>();
try
{
Expand Down
53 changes: 51 additions & 2 deletions src/Backend/Store/Implementations/Build/RecipeUtils.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2016 Bastian Eicher, Roland Leopold Walkling
* Copyright 2010-2017 Bastian Eicher
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser Public License as published by
Expand Down Expand Up @@ -78,7 +78,8 @@ public static TemporaryDirectory Apply([NotNull] this Recipe recipe, [NotNull, I
step.Apply(downloadedEnum.Current, workingDir);
},
(RemoveStep step) => step.Apply(workingDir),
(RenameStep step) => step.Apply(workingDir)
(RenameStep step) => step.Apply(workingDir),
(CopyFromStep step) => step.Apply(workingDir, handler, tag)
}.Dispatch(recipe.Steps);
// ReSharper restore AccessToDisposedClosure
}
Expand Down Expand Up @@ -251,5 +252,53 @@ public static void Apply([NotNull] this RenameStep step, [NotNull] TemporaryDire
FlagUtils.Rename(Path.Combine(workingDir, FlagUtils.XbitFile), source, destination);
FlagUtils.Rename(Path.Combine(workingDir, FlagUtils.SymlinkFile), source, destination);
}

/// <summary>
/// Applies a <see cref="CopyFromStep"/> to a <see cref="TemporaryDirectory"/>.
/// </summary>
/// <param name="step">The <see cref="Archive"/> to apply.</param>
/// <param name="workingDir">The <see cref="TemporaryDirectory"/> to apply the changes to.</param>
/// <param name="handler">A callback object used when the the user needs to be informed about progress.</param>
/// <param name="tag">A tag used to associate composite task with a specific operation; can be null.</param>
/// <exception cref="IOException">A path specified in <paramref name="step"/> is illegal.</exception>
public static void Apply([NotNull] this CopyFromStep step, [NotNull] TemporaryDirectory workingDir, ITaskHandler handler, [CanBeNull] object tag = null)
{
#region Sanity checks
if (step == null) throw new ArgumentNullException(nameof(step));
if (workingDir == null) throw new ArgumentNullException(nameof(workingDir));
#endregion

#region Path validation
string source = FileUtils.UnifySlashes(step.Source ?? "");
string destination = FileUtils.UnifySlashes(step.Destination ?? "");
if (FileUtils.IsBreakoutPath(source)) throw new IOException(string.Format(Resources.RecipeInvalidPath, source));
if (FileUtils.IsBreakoutPath(destination)) throw new IOException(string.Format(Resources.RecipeInvalidPath, destination));
#endregion

var store = StoreFactory.CreateDefault();
string sourcePath = Path.Combine(store.GetPath(step.Implementation), source);

if (Directory.Exists(sourcePath))
{
handler.RunTask(new CloneDirectory(sourcePath, workingDir)
{
TargetSuffix = destination,
UseHardlinks = true,
Tag = tag
});
}
else if (File.Exists(sourcePath))
{
if (string.IsNullOrEmpty(destination)) throw new IOException(string.Format(Resources.RecipeCopyFromDestinationMissing, step));
handler.RunTask(new CloneFile(sourcePath, workingDir)
{
TargetSuffix = Path.GetDirectoryName(destination),
TargetFileName = Path.GetFileName(destination),
UseHardlinks = true,
Tag = tag
});
}
else throw new IOException(string.Format(Resources.RecipeCopyFromSourceMissing, step));
}
}
}
18 changes: 18 additions & 0 deletions src/Backend/Store/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/Backend/Store/Properties/Resources.de.resx
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,12 @@ Sie können die Datei '{0}' löschen, um das Problem zu beheben.</value>
<value>Es gab ein Problem beim Laden des Konfigurationswerts '{0}'.
Sie können die Datei '{1}' löschen, um das Problem zu beheben. Dabei gehen womöglich auch andere Einstellungen verloren.</value>
</data>
<data name="RecipeCopyFromDestinationMissing" xml:space="preserve">
<value>Das Ziel für den '{0}' Schritt muss gesetzt sein, da die Quelle eine Datei und kein Verzeichnis ist.</value>
</data>
<data name="RecipeCopyFromSourceMissing" xml:space="preserve">
<value>Die Quelle, die im '{0}' Schritt angegeben ist, existiert nicht.</value>
</data>
<data name="RecipeFileNotDownloaded" xml:space="preserve">
<value>Eine Datei auf die im &lt;recipe&gt; verwießen wird wurde nicht heruntergeladen.</value>
</data>
Expand Down
6 changes: 6 additions & 0 deletions src/Backend/Store/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,12 @@ You can delete the file '{0}' to fix the problem.</value>
<value>There was a problem loading the configuration value '{0}'.
You can delete the file '{1}' to fix the problem. Other settings may also be lost.</value>
</data>
<data name="RecipeCopyFromDestinationMissing" xml:space="preserve">
<value>The destination for the '{0}' step must be specified because the source is a file and not a directory.</value>
</data>
<data name="RecipeCopyFromSourceMissing" xml:space="preserve">
<value>The source specified in the '{0}' step does not exist.</value>
</data>
<data name="RecipeFileNotDownloaded" xml:space="preserve">
<value>A file referenced in the &lt;recipe&gt; was not downloaded.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

using System;
using System.IO;
using System.Linq;
using FluentAssertions;
using NanoByte.Common.Storage;
using NanoByte.Common.Streams;
Expand Down Expand Up @@ -169,6 +170,71 @@ public void TestApplyRecipeRename()
}
}

[Test]
public void TestApplyRecipeCopyFrom()
{
using (new LocationsRedirect("0install-unit-tests"))
{
string existingImplPath = Path.Combine(StoreConfig.GetImplementationDirs().First(), "sha1new=123");
new TestRoot
{
new TestDirectory("source")
{
new TestFile("file"),
new TestFile("executable") {IsExecutable = true},
new TestSymlink("symlink", "target")
}
}.Build(existingImplPath);

using (var archiveFile = new TemporaryFile("0install-unit-tests"))
{
typeof(ArchiveExtractorTest).CopyEmbeddedToFile("testArchive.zip", archiveFile);

var downloadedFiles = new[] {archiveFile};
var recipe = new Recipe
{
Steps =
{
new CopyFromStep
{
Source = "source",
Destination = "dest",
Implementation = new Implementation
{
ManifestDigest = new ManifestDigest(sha1New: "123")
}
},
new CopyFromStep
{
Source = "source/file",
Destination = "dest/symlink", // Overwrite existing symlink with regular file
Implementation = new Implementation
{
ManifestDigest = new ManifestDigest(sha1New: "123")
}
}
}
};

using (var recipeDir = recipe.Apply(downloadedFiles, new SilentTaskHandler()))
{
new TestRoot
{
new TestDirectory("dest")
{
new TestFile("file"),
new TestFile("executable") {IsExecutable = true},
new TestFile("symlink")
}
}.Verify(recipeDir);
}
}

FileUtils.DisableWriteProtection(existingImplPath);
Directory.Delete(existingImplPath, recursive: true);
}
}

[Test]
public void TestApplyRecipeExceptions()
{
Expand Down

0 comments on commit 928485b

Please sign in to comment.