Skip to content

Commit f27d52f

Browse files
committed
[Xamarin.Android.Build.Tasks] Path is too long
Fixes https://bugzilla.xamarin.com/show_bug.cgi?id=30147 Windows (still) has a max path limit of 260 characters.. This can cause us a problem if the user creates a project in a directory which already takes up most of that limit.. Like on their Desktop! This is because of the intermediate structure that was introduced to handle embedding resources into the assemblies. The current intermediate structure is as follows `$(IntermediateOutputPath)\__library_project_imports__\$(AssemblyName)\library_project_imports\` `$(IntermediateOutputPath)\__library_project_imports__\$(AssemblyName)\native_library_imports\` Now consider that `$(AssemblyName)` can sometimes end up with something lile "Xamarin.Android.Support.v7.AppCompat.21.0.3.0" you can easily see how we start getting into trouble. There was an attempt to fix this up a while ago (722dcc05) by introducing the `$(UseShortFileNames)` property. However while this did introduce a directory structure which was shorter is broke backwards compatability with older versions of xamarin-android. This is because of the structore of the zip files we that are embedded into the assemblies. The current system just extracts the zip into the `$(IntermediateOutputPath)\__library_project_imports__\$(AssemblyName)\` directory and assumes that it contains a `library_project_imports` directory. All the build tasks also assumed that as well. So the fix needs to be done on a number of fronts. Firstly we need to update the `$(_LibraryProjectImportsDirectoryName)` and `$(_NativeLibraryImportsDirectoryName)` to be something shorter. Next up we need to shorten "__library_project_imports__" to something else verbose as well. This should cut down on the amount of the MAX_PATH we chew up. The real key to this is to NOT change the structure of the zip files in the assemblies! So when we generate a zip from "jlibs" we make sure that the folder in the zip is called "library_project_imports". And when we extract the zip file we makle sure that "library_project_imports" is replaced by the shorter name. This will ensure that we are backward compatbile with older versions BUT more importantly we get to use the shorter directory structure. The files for native librarys are not extracted to disk but are extracted from memory so as long as the structure remains the same i.e "native_library_imports" that code does not need to change. The other thing we need to do is to update ResolveLibraryProjectImports Task to upgrade the system when it runs. So if we already have a "libraryprojectimports.cache" in place we just use that as is. But if we re-run the ResolveLibraryProjectImports task (due to a change or a clean build) we detect if we have the older structure in place and just remove it.. Since we are going to regenerate the entire cache again anyway we might as well start from scratch. With this in place it becomes possible we can now enable `$(UseShortFileNames)` by default! So the new structure that is created is as follows `$(IntermediateOutputPath)\lp\<Hash>\jl` `$(IntermediateOutputPath)\lp\<Hash>\nl` The <Hash> will be the MD2 hash of the assemblyName which produces a 10 digit value. The old behaviour can be enabled by setting `$(UseShortFileNames)` to `False`. Note in order to keep existing bindings generator behaviour consistent, the BindingsGenerator task will not use the `$(UseShortFileNames)` property to control how it generates its .cs files. Instead a new property `$(UseShortGeneratorFileNames)` which can be used to control if the generator produces short names (e.g 1.cs, 2.cs). This will be `False` by default.
1 parent df79d3d commit f27d52f

File tree

10 files changed

+179
-127
lines changed

10 files changed

+179
-127
lines changed

src/Xamarin.Android.Build.Tasks/Tasks/CreateLibraryResourceArchive.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public override bool Execute ()
9494

9595
if (Files.ArchiveZip (outpath, f => {
9696
using (var zip = new ZipArchiveEx (f)) {
97-
zip.AddDirectory (OutputDirectory, outDirInfo.Name);
97+
zip.AddDirectory (OutputDirectory, "library_project_imports");
9898
}
9999
})) {
100100
Log.LogDebugMessage ("Saving contents to " + outpath);

src/Xamarin.Android.Build.Tasks/Tasks/CreateManagedLibraryResourceArchive.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public override bool Execute ()
108108

109109
if (Files.ArchiveZip (outpath, f => {
110110
using (var zip = new ZipArchiveEx (f)) {
111-
zip.AddDirectory (OutputDirectory, outDirInfo.Name);
111+
zip.AddDirectory (OutputDirectory, "library_project_imports");
112112
}
113113
})) {
114114
Log.LogDebugMessage ("Saving contents to " + outpath);

src/Xamarin.Android.Build.Tasks/Tasks/CreateNativeLibraryArchive.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public override bool Execute ()
6666

6767
if (Files.ArchiveZip (outpath, f => {
6868
using (var zip = new ZipArchiveEx (f)) {
69-
zip.AddDirectory (OutputDirectory, outDirInfo.Name);
69+
zip.AddDirectory (OutputDirectory, "native_library_imports");
7070
}
7171
})) {
7272
Log.LogDebugMessage ("Saving contents to " + outpath);

src/Xamarin.Android.Build.Tasks/Tasks/ResolveLibraryProjectImports.cs

Lines changed: 96 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,12 @@ public class ResolveLibraryProjectImports : Task
5151
[Output]
5252
public ITaskItem [] ResolvedResourceDirectoryStamps { get; set; }
5353

54-
string imports_dir = "library_project_imports";
55-
5654
public ResolveLibraryProjectImports ()
5755
{
5856
}
5957

6058
// Extracts library project contents under e.g. obj/Debug/[__library_projects__/*.jar | res/*/*]
59+
// Extracts library project contents under e.g. obj/Debug/[lp/*.jar | res/*/*]
6160
public override bool Execute ()
6261
{
6362
Log.LogDebugMessage ("ResolveLibraryProjectImports Task");
@@ -135,13 +134,20 @@ static string GetTargetAssembly (ITaskItem assemblyName)
135134
}
136135

137136
// Extracts library project contents under e.g. obj/Debug/[__library_projects__/*.jar | res/*/*]
137+
// Extracts library project contents under e.g. obj/Debug/[lp/*.jar | res/*/*]
138138
void Extract (
139139
DirectoryAssemblyResolver res,
140140
ICollection<string> jars,
141141
ICollection<string> resolvedResourceDirectories,
142142
ICollection<string> resolvedAssetDirectories,
143143
ICollection<string> resolvedEnvironments)
144144
{
145+
// lets "upgrade" the old directory.
146+
string oldPath = Path.GetFullPath (Path.Combine (OutputImportDirectory, "..", "__library_projects__"));
147+
if (!OutputImportDirectory.Contains ("__library_projects__") && Directory.Exists (oldPath)) {
148+
MonoAndroidHelper.SetDirectoryWriteable (oldPath);
149+
Directory.Delete (oldPath, recursive: true);
150+
}
145151
var outdir = new DirectoryInfo (OutputImportDirectory);
146152
if (!outdir.Exists)
147153
outdir.Create ();
@@ -154,26 +160,95 @@ void Extract (
154160
.Select (a => GetTargetAssembly (a))
155161
.Where (a => a != null)
156162
.Distinct ()) {
157-
foreach (var imp in new string [] {imports_dir, "library_project_imports"}.Distinct ()) {
158-
string assemblyIdentName = Path.GetFileNameWithoutExtension (assemblyPath);
159-
if (UseShortFileNames) {
160-
assemblyIdentName = Xamarin.Android.Tasks.MonoAndroidHelper.GetLibraryImportDirectoryNameForAssembly (assemblyIdentName);
161-
}
162-
string outDirForDll = Path.Combine (OutputImportDirectory, assemblyIdentName);
163-
string importsDir = Path.Combine (outDirForDll, imp);
163+
string assemblyIdentName = Path.GetFileNameWithoutExtension (assemblyPath);
164+
if (UseShortFileNames) {
165+
assemblyIdentName = Xamarin.Android.Tasks.MonoAndroidHelper.GetLibraryImportDirectoryNameForAssembly (assemblyIdentName);
166+
}
167+
string outDirForDll = Path.Combine (OutputImportDirectory, assemblyIdentName);
168+
string importsDir = Path.Combine (outDirForDll, ImportsDirectory);
169+
#if SEPARATE_CRUNCH
170+
// FIXME: review these binResDir thing and enable this. Eclipse does this.
171+
// Enabling these blindly causes build failure on ActionBarSherlock.
172+
//string binResDir = Path.Combine (importsDir, "bin", "res");
173+
//string binAssemblyDir = Path.Combine (importsDir, "bin", "assets");
174+
#endif
175+
string resDir = Path.Combine (importsDir, "res");
176+
string assemblyDir = Path.Combine (importsDir, "assets");
177+
178+
// Skip already-extracted resources.
179+
var stamp = new FileInfo (Path.Combine (outdir.FullName, assemblyIdentName + ".stamp"));
180+
if (stamp.Exists && stamp.LastWriteTime > new FileInfo (assemblyPath).LastWriteTime) {
181+
Log.LogDebugMessage ("Skipped resource lookup for {0}: extracted files are up to date", assemblyPath);
164182
#if SEPARATE_CRUNCH
165-
// FIXME: review these binResDir thing and enable this. Eclipse does this.
183+
// FIXME: review these binResDir/binAssemblyDir thing and enable this. Eclipse does this.
166184
// Enabling these blindly causes build failure on ActionBarSherlock.
167-
//string binResDir = Path.Combine (importsDir, "bin", "res");
168-
//string binAssemblyDir = Path.Combine (importsDir, "bin", "assets");
185+
if (Directory.Exists (binResDir))
186+
resolvedResourceDirectories.Add (binResDir);
187+
if (Directory.Exists (binAssemblyDir))
188+
resolvedAssetDirectories.Add (binAssemblyDir);
169189
#endif
170-
string resDir = Path.Combine (importsDir, "res");
171-
string assemblyDir = Path.Combine (importsDir, "assets");
190+
if (Directory.Exists (resDir))
191+
resolvedResourceDirectories.Add (resDir);
192+
if (Directory.Exists (assemblyDir))
193+
resolvedAssetDirectories.Add (assemblyDir);
194+
continue;
195+
}
196+
197+
if (Directory.Exists (outDirForDll))
198+
Directory.Delete (outDirForDll, true);
199+
200+
var assembly = res.GetAssembly (assemblyPath);
201+
202+
foreach (var mod in assembly.Modules) {
203+
// android environment files
204+
foreach (var envtxt in mod.Resources
205+
.Where (r => r.Name.StartsWith ("__AndroidEnvironment__", StringComparison.OrdinalIgnoreCase))
206+
.Where (r => r is EmbeddedResource)
207+
.Cast<EmbeddedResource> ()) {
208+
if (!Directory.Exists (outDirForDll))
209+
Directory.CreateDirectory (outDirForDll);
210+
var finfo = new FileInfo (Path.Combine (outDirForDll, envtxt.Name));
211+
using (var fs = finfo.Create ()) {
212+
var data = envtxt.GetResourceData ();
213+
fs.Write (data, 0, data.Length);
214+
}
215+
resolvedEnvironments.Add (finfo.FullName);
216+
}
217+
218+
// embedded jars (EmbeddedJar, EmbeddedReferenceJar)
219+
var resjars = mod.Resources
220+
.Where (r => r.Name.EndsWith (".jar", StringComparison.InvariantCultureIgnoreCase))
221+
.Select (r => (EmbeddedResource) r);
222+
foreach (var resjar in resjars) {
223+
var data = resjar.GetResourceData ();
224+
if (!Directory.Exists (importsDir))
225+
Directory.CreateDirectory (importsDir);
226+
using (var outfs = File.Create (Path.Combine (importsDir, resjar.Name)))
227+
outfs.Write (data, 0, data.Length);
228+
}
229+
230+
// embedded AndroidResourceLibrary archive
231+
var reszip = mod.Resources.FirstOrDefault (r => r.Name == "__AndroidLibraryProjects__.zip") as EmbeddedResource;
232+
if (reszip != null) {
233+
if (!Directory.Exists (outDirForDll))
234+
Directory.CreateDirectory (outDirForDll);
235+
var finfo = new FileInfo (Path.Combine (outDirForDll, reszip.Name));
236+
using (var fs = finfo.Create ()) {
237+
var data = reszip.GetResourceData ();
238+
fs.Write (data, 0, data.Length);
239+
}
240+
241+
// temporarily extracted directory will look like:
242+
// __library_projects__/[dllname]/[library_project_imports | jlibs]/bin
243+
using (var zip = MonoAndroidHelper.ReadZipFile (finfo.FullName)) {
244+
Files.ExtractAll (zip, outDirForDll, modifyCallback: (entryFullName) => {
245+
return entryFullName.Replace ("library_project_imports", ImportsDirectory);
246+
});
247+
}
172248

173-
// Skip already-extracted resources.
174-
var stamp = new FileInfo (Path.Combine (outdir.FullName, assemblyIdentName + ".stamp"));
175-
if (stamp.Exists && stamp.LastWriteTime > new FileInfo (assemblyPath).LastWriteTime) {
176-
Log.LogDebugMessage ("Skipped resource lookup for {0}: extracted files are up to date", assemblyPath);
249+
// We used to *copy* the resources to overwrite other resources,
250+
// which resulted in missing resource issue.
251+
// Here we replaced copy with use of '-S' option and made it to work.
177252
#if SEPARATE_CRUNCH
178253
// FIXME: review these binResDir/binAssemblyDir thing and enable this. Eclipse does this.
179254
// Enabling these blindly causes build failure on ActionBarSherlock.
@@ -186,80 +261,13 @@ void Extract (
186261
resolvedResourceDirectories.Add (resDir);
187262
if (Directory.Exists (assemblyDir))
188263
resolvedAssetDirectories.Add (assemblyDir);
189-
continue;
190-
}
191-
192-
if (Directory.Exists (outDirForDll))
193-
Directory.Delete (outDirForDll, true);
194-
195-
Directory.CreateDirectory (importsDir);
196-
197-
var assembly = res.GetAssembly (assemblyPath);
198-
199-
foreach (var mod in assembly.Modules) {
200-
// android environment files
201-
foreach (var envtxt in mod.Resources
202-
.Where (r => r.Name.StartsWith ("__AndroidEnvironment__", StringComparison.OrdinalIgnoreCase))
203-
.Where (r => r is EmbeddedResource)
204-
.Cast<EmbeddedResource> ()) {
205-
if (!Directory.Exists (outDirForDll))
206-
Directory.CreateDirectory (outDirForDll);
207-
var finfo = new FileInfo (Path.Combine (outDirForDll, envtxt.Name));
208-
using (var fs = finfo.Create ()) {
209-
var data = envtxt.GetResourceData ();
210-
fs.Write (data, 0, data.Length);
211-
}
212-
resolvedEnvironments.Add (finfo.FullName);
213-
}
214-
215-
// embedded jars (EmbeddedJar, EmbeddedReferenceJar)
216-
var resjars = mod.Resources
217-
.Where (r => r.Name.EndsWith (".jar", StringComparison.InvariantCultureIgnoreCase))
218-
.Select (r => (EmbeddedResource) r);
219-
foreach (var resjar in resjars) {
220-
var data = resjar.GetResourceData ();
221-
using (var outfs = File.Create (Path.Combine (importsDir, resjar.Name)))
222-
outfs.Write (data, 0, data.Length);
223-
}
224-
225-
// embedded AndroidResourceLibrary archive
226-
var reszip = mod.Resources.FirstOrDefault (r => r.Name == "__AndroidLibraryProjects__.zip") as EmbeddedResource;
227-
if (reszip != null) {
228-
if (!Directory.Exists (outDirForDll))
229-
Directory.CreateDirectory (outDirForDll);
230-
var finfo = new FileInfo (Path.Combine (outDirForDll, reszip.Name));
231-
using (var fs = finfo.Create ()) {
232-
var data = reszip.GetResourceData ();
233-
fs.Write (data, 0, data.Length);
234-
}
235-
236-
// temporarily extracted directory will look like:
237-
// __library_projects__/[dllname]/[library_project_imports | jlibs]/bin
238-
using (var zip = MonoAndroidHelper.ReadZipFile (finfo.FullName))
239-
Files.ExtractAll (zip, outDirForDll);
240-
241-
// We used to *copy* the resources to overwrite other resources,
242-
// which resulted in missing resource issue.
243-
// Here we replaced copy with use of '-S' option and made it to work.
244-
#if SEPARATE_CRUNCH
245-
// FIXME: review these binResDir/binAssemblyDir thing and enable this. Eclipse does this.
246-
// Enabling these blindly causes build failure on ActionBarSherlock.
247-
if (Directory.Exists (binResDir))
248-
resolvedResourceDirectories.Add (binResDir);
249-
if (Directory.Exists (binAssemblyDir))
250-
resolvedAssetDirectories.Add (binAssemblyDir);
251-
#endif
252-
if (Directory.Exists (resDir))
253-
resolvedResourceDirectories.Add (resDir);
254-
if (Directory.Exists (assemblyDir))
255-
resolvedAssetDirectories.Add (assemblyDir);
256264

257-
finfo.Delete ();
258-
}
265+
finfo.Delete ();
259266
}
267+
}
260268

269+
if (Directory.Exists (importsDir))
261270
stamp.Create ().Close ();
262-
}
263271
}
264272

265273
foreach (var f in outdir.GetFiles ("*.jar")

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BindingBuildTest.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,8 @@ public void BindingCheckHiddenFiles ()
280280
proj.OtherBuildItems.Add (new BuildItem ("ProjectReference", "..\\Binding\\UnnamedProject.csproj"));
281281
using (var b = CreateApkBuilder (Path.Combine ("temp", "BindingCheckHiddenFiles", "App"))) {
282282
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
283-
var dsStorePath = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, "__library_projects__",
284-
"UnnamedProject", "library_project_imports");
283+
var dsStorePath = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, "lp",
284+
"E95D57CD3A", "jl");
285285
Assert.IsTrue (Directory.Exists (dsStorePath), "{0} should exist.", dsStorePath);
286286
Assert.IsFalse (File.Exists (Path.Combine (dsStorePath, ".DS_Store")), "{0} should NOT exist.",
287287
Path.Combine (dsStorePath, ".DS_Store"));

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,6 +1740,49 @@ public void BuildInDesignTimeMode ()
17401740
}
17411741
}
17421742

1743+
[Test]
1744+
public void CheckLibraryImportsUpgrade ([Values(false, true)] bool useShortFileNames)
1745+
{
1746+
var path = Path.Combine ("temp", "CheckLibraryImportsUpgrade");
1747+
var libproj = new XamarinAndroidLibraryProject () {
1748+
IsRelease = true,
1749+
ProjectName = "Library1"
1750+
};
1751+
var proj = new XamarinAndroidApplicationProject () {
1752+
IsRelease = true,
1753+
ProjectName = "App1",
1754+
};
1755+
proj.References.Add (new BuildItem ("ProjectReference", $"..\\Library1\\Library1.csproj"));
1756+
proj.SetProperty ("_AndroidLibrayProjectIntermediatePath", Path.Combine (proj.IntermediateOutputPath, "__library_projects__"));
1757+
proj.SetProperty (proj.ActiveConfigurationProperties, "UseShortFileNames", useShortFileNames);
1758+
using (var libb = CreateDllBuilder (Path.Combine (path, libproj.ProjectName), false, false)) {
1759+
Assert.IsTrue (libb.Build (libproj), "Build should have succeeded.");
1760+
using (var builder = CreateApkBuilder (Path.Combine (path, proj.ProjectName), false, false)) {
1761+
builder.Verbosity = LoggerVerbosity.Diagnostic;
1762+
Assert.IsTrue (builder.Build (proj), "Build should have succeeded.");
1763+
Assert.IsTrue (Directory.Exists (Path.Combine (Root, path, proj.ProjectName, proj.IntermediateOutputPath, "__library_projects__")),
1764+
"The __library_projects__ directory should exist.");
1765+
proj.RemoveProperty ("_AndroidLibrayProjectIntermediatePath");
1766+
Assert.IsTrue (builder.Build (proj), "Build should have succeeded.");
1767+
Assert.IsTrue (Directory.Exists (Path.Combine (Root, path, proj.ProjectName, proj.IntermediateOutputPath, "__library_projects__")),
1768+
"The __library_projects__ directory should exist.");
1769+
Assert.IsTrue (libb.Clean (libproj), "Clean should have succeeded.");
1770+
Assert.IsTrue (libb.Build (libproj), "Build should have succeeded.");
1771+
Assert.IsTrue (builder.Build (proj), "Build should have succeeded.");
1772+
if (!useShortFileNames) {
1773+
Assert.IsTrue (Directory.Exists (Path.Combine (Root, path, proj.ProjectName, proj.IntermediateOutputPath, "__library_projects__")),
1774+
"The __library_projects__ directory should exist.");
1775+
} else {
1776+
Assert.IsFalse (Directory.Exists (Path.Combine (Root, path, proj.ProjectName, proj.IntermediateOutputPath, "__library_projects__")),
1777+
"The __library_projects__ directory should not exist.");
1778+
Assert.IsTrue (Directory.Exists (Path.Combine (Root, path, proj.ProjectName, proj.IntermediateOutputPath, "lp")),
1779+
"The lp directory should exist.");
1780+
}
1781+
1782+
}
1783+
}
1784+
}
1785+
17431786
[Test]
17441787
public void BuildAMassiveApp()
17451788
{

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public Class2 ()
171171
Assert.IsNotNull (libfoo, "libfoo.so should exist in the .apk");
172172

173173
Assert.AreEqual (so.TextContent ().Length, new FileInfo (Path.Combine (Root, libbuilder.ProjectDirectory, lib.IntermediateOutputPath,
174-
"native_library_imports", "armeabi-v7a", "libfoo.so")).Length,
174+
"nl", "armeabi-v7a", "libfoo.so")).Length,
175175
"intermediate size mismatch");
176176
libfoo = ZipHelper.ReadFileFromZip (Path.Combine (Root, builder.ProjectDirectory, app.OutputPath, app.PackageName + ".apk"),
177177
"lib/armeabi-v7a/libfoo.so");

src/Xamarin.Android.Build.Tasks/Utilities/Files.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ public static ZipArchive ReadZipFile (string filename, bool strictConsistencyChe
193193
return ZipArchive.Open (filename, FileMode.Open, strictConsistencyChecks: strictConsistencyChecks);
194194
}
195195

196-
public static void ExtractAll(ZipArchive zip, string destination, Action<int, int> progressCallback = null)
196+
public static void ExtractAll(ZipArchive zip, string destination, Action<int, int> progressCallback = null, Func<string, string> modifyCallback = null)
197197
{
198198
int i = 0;
199199
int total = (int)zip.EntryCount;
@@ -202,14 +202,15 @@ public static void ExtractAll(ZipArchive zip, string destination, Action<int, in
202202
entry.FullName.EndsWith ("/__MACOSX", StringComparison.OrdinalIgnoreCase) ||
203203
entry.FullName.EndsWith ("/.DS_Store", StringComparison.OrdinalIgnoreCase))
204204
continue;
205+
var fullName = modifyCallback != null ? modifyCallback (entry.FullName) : entry.FullName;
205206
if (entry.IsDirectory) {
206-
Directory.CreateDirectory (Path.Combine (destination, entry.FullName));
207+
Directory.CreateDirectory (Path.Combine (destination, fullName));
207208
continue;
208209
}
209210
if (progressCallback != null)
210211
progressCallback (i++, total);
211-
Directory.CreateDirectory (Path.Combine (destination, Path.GetDirectoryName (entry.FullName)));
212-
entry.Extract (destination, entry.FullName);
212+
Directory.CreateDirectory (Path.Combine (destination, Path.GetDirectoryName (fullName)));
213+
entry.Extract (destination, fullName);
213214
}
214215
}
215216

0 commit comments

Comments
 (0)