Пример #1
0
        // Extracts library project contents under e.g. obj/Debug/[__library_projects__/*.jar | res/*/*]
        // Extracts library project contents under e.g. obj/Debug/[lp/*.jar | res/*/*]
        void Extract(
            DirectoryAssemblyResolver res,
            ICollection <string> jars,
            ICollection <string> resolvedResourceDirectories,
            ICollection <string> resolvedAssetDirectories,
            ICollection <string> resolvedEnvironments)
        {
            // lets "upgrade" the old directory.
            string oldPath = Path.GetFullPath(Path.Combine(OutputImportDirectory, "..", "__library_projects__"));

            if (!OutputImportDirectory.Contains("__library_projects__") && Directory.Exists(oldPath))
            {
                MonoAndroidHelper.SetDirectoryWriteable(Path.Combine(oldPath, ".."));
                Directory.Delete(oldPath, recursive: true);
            }
            var outdir = new DirectoryInfo(OutputImportDirectory);

            if (!outdir.Exists)
            {
                outdir.Create();
            }

            foreach (var assembly in Assemblies)
            {
                res.Load(assembly.ItemSpec);
            }

            // FIXME: reorder references by import priority (not sure how to do that yet)
            foreach (var assemblyPath in Assemblies
                     .Select(a => GetTargetAssembly(a))
                     .Where(a => a != null)
                     .Distinct())
            {
                string assemblyIdentName = Path.GetFileNameWithoutExtension(assemblyPath);
                if (UseShortFileNames)
                {
                    assemblyIdentName = assemblyMap.GetLibraryImportDirectoryNameForAssembly(assemblyIdentName);
                }
                string outDirForDll = Path.Combine(OutputImportDirectory, assemblyIdentName);
                string importsDir   = Path.Combine(outDirForDll, ImportsDirectory);
#if SEPARATE_CRUNCH
                // FIXME: review these binResDir thing and enable this. Eclipse does this.
                // Enabling these blindly causes build failure on ActionBarSherlock.
                //string binResDir = Path.Combine (importsDir, "bin", "res");
                //string binAssemblyDir = Path.Combine (importsDir, "bin", "assets");
#endif
                string resDir      = Path.Combine(importsDir, "res");
                string assemblyDir = Path.Combine(importsDir, "assets");

                // Skip already-extracted resources.
                var stamp = new FileInfo(Path.Combine(outdir.FullName, assemblyIdentName + ".stamp"));
                if (stamp.Exists && stamp.LastWriteTimeUtc > new FileInfo(assemblyPath).LastWriteTimeUtc)
                {
                    Log.LogDebugMessage("Skipped resource lookup for {0}: extracted files are up to date", assemblyPath);
#if SEPARATE_CRUNCH
                    // FIXME: review these binResDir/binAssemblyDir thing and enable this. Eclipse does this.
                    // Enabling these blindly causes build failure on ActionBarSherlock.
                    if (Directory.Exists(binResDir))
                    {
                        resolvedResourceDirectories.Add(binResDir);
                    }
                    if (Directory.Exists(binAssemblyDir))
                    {
                        resolvedAssetDirectories.Add(binAssemblyDir);
                    }
#endif
                    if (Directory.Exists(resDir))
                    {
                        resolvedResourceDirectories.Add(resDir);
                    }
                    if (Directory.Exists(assemblyDir))
                    {
                        resolvedAssetDirectories.Add(assemblyDir);
                    }
                    foreach (var env in Directory.EnumerateFiles(outDirForDll, "__AndroidEnvironment__*", SearchOption.TopDirectoryOnly))
                    {
                        resolvedEnvironments.Add(env);
                    }
                    continue;
                }

                Log.LogDebugMessage("Refreshing {0}", assemblyPath);

                Directory.CreateDirectory(importsDir);

                var  assembly          = res.GetAssembly(assemblyPath);
                var  assemblyLastWrite = new FileInfo(assemblyPath).LastWriteTimeUtc;
                bool updated           = false;

                foreach (var mod in assembly.Modules)
                {
                    // android environment files
                    foreach (var envtxt in mod.Resources
                             .Where(r => r.Name.StartsWith("__AndroidEnvironment__", StringComparison.OrdinalIgnoreCase))
                             .Where(r => r is EmbeddedResource)
                             .Cast <EmbeddedResource> ())
                    {
                        if (!Directory.Exists(outDirForDll))
                        {
                            Directory.CreateDirectory(outDirForDll);
                        }
                        var finfo = new FileInfo(Path.Combine(outDirForDll, envtxt.Name));
                        if (!finfo.Exists || finfo.LastWriteTimeUtc > assemblyLastWrite)
                        {
                            using (var fs = finfo.Create()) {
                                var data = envtxt.GetResourceData();
                                fs.Write(data, 0, data.Length);
                            }
                            updated = true;
                        }
                        resolvedEnvironments.Add(finfo.FullName);
                    }

                    // embedded jars (EmbeddedJar, EmbeddedReferenceJar)
                    var resjars = mod.Resources
                                  .Where(r => r.Name.EndsWith(".jar", StringComparison.InvariantCultureIgnoreCase))
                                  .Select(r => (EmbeddedResource)r);
                    foreach (var resjar in resjars)
                    {
                        var outjarFile = Path.Combine(importsDir, resjar.Name);
                        var fi         = new FileInfo(outjarFile);
                        if (!fi.Exists || fi.LastWriteTimeUtc > assemblyLastWrite)
                        {
                            var data = resjar.GetResourceData();
                            using (var outfs = File.Create(outjarFile))
                                outfs.Write(data, 0, data.Length);
                            updated = true;
                        }
                        jars.Add(outjarFile);
                    }

                    // embedded AndroidResourceLibrary archive
                    var reszip = mod.Resources.FirstOrDefault(r => r.Name == "__AndroidLibraryProjects__.zip") as EmbeddedResource;
                    if (reszip != null)
                    {
                        if (!Directory.Exists(outDirForDll))
                        {
                            Directory.CreateDirectory(outDirForDll);
                        }
                        var finfo = new FileInfo(Path.Combine(outDirForDll, reszip.Name));
                        using (var fs = finfo.Create()) {
                            var data = reszip.GetResourceData();
                            fs.Write(data, 0, data.Length);
                        }

                        // temporarily extracted directory will look like:
                        //    __library_projects__/[dllname]/[library_project_imports | jlibs]/bin
                        using (var zip = MonoAndroidHelper.ReadZipFile(finfo.FullName)) {
                            try {
                                updated |= Files.ExtractAll(zip, importsDir, modifyCallback: (entryFullName) => {
                                    return(entryFullName
                                           .Replace("library_project_imports\\", "")
                                           .Replace("library_project_imports/", ""));
                                }, deleteCallback: (fileToDelete) => {
                                    return(!jars.Contains(fileToDelete));
                                }, forceUpdate: false);
                            } catch (PathTooLongException ex) {
                                Log.LogErrorFromException(new PathTooLongException($"Error extracting resources from \"{assemblyPath}\"", ex));
                            }
                        }

                        // We used to *copy* the resources to overwrite other resources,
                        // which resulted in missing resource issue.
                        // Here we replaced copy with use of '-S' option and made it to work.
#if SEPARATE_CRUNCH
                        // FIXME: review these binResDir/binAssemblyDir thing and enable this. Eclipse does this.
                        // Enabling these blindly causes build failure on ActionBarSherlock.
                        if (Directory.Exists(binResDir))
                        {
                            resolvedResourceDirectories.Add(binResDir);
                        }
                        if (Directory.Exists(binAssemblyDir))
                        {
                            resolvedAssetDirectories.Add(binAssemblyDir);
                        }
#endif
                        if (Directory.Exists(resDir))
                        {
                            resolvedResourceDirectories.Add(resDir);
                        }
                        if (Directory.Exists(assemblyDir))
                        {
                            resolvedAssetDirectories.Add(assemblyDir);
                        }

                        finfo.Delete();
                    }
                }

                if (Directory.Exists(importsDir) && (updated || !stamp.Exists))
                {
                    Log.LogDebugMessage("Touch {0}", stamp.FullName);
                    stamp.Create().Close();
                }
            }
            foreach (var f in outdir.GetFiles("*.jar", SearchOption.AllDirectories)
                     .Select(fi => fi.FullName))
            {
                if (jars.Contains(f))
                {
                    continue;
                }
                jars.Add(f);
            }
        }
Пример #2
0
        // Extracts library project contents under e.g. obj/Debug/[__library_projects__/*.jar | res/*/*]
        // Extracts library project contents under e.g. obj/Debug/[lp/*.jar | res/*/*]
        void Extract(
            IDictionary <string, ITaskItem> jars,
            ICollection <ITaskItem> resolvedResourceDirectories,
            ICollection <ITaskItem> resolvedAssetDirectories,
            ICollection <ITaskItem> resolvedEnvironments)
        {
            // lets "upgrade" the old directory.
            string oldPath = Path.GetFullPath(Path.Combine(OutputImportDirectory, "..", "__library_projects__"));

            if (!OutputImportDirectory.Contains("__library_projects__") && Directory.Exists(oldPath))
            {
                MonoAndroidHelper.SetDirectoryWriteable(Path.Combine(oldPath, ".."));
                Directory.Delete(oldPath, recursive: true);
            }
            var outdir = Path.GetFullPath(OutputImportDirectory);

            Directory.CreateDirectory(outdir);

            bool skip;

            foreach (var assemblyItem in Assemblies)
            {
                var assemblyPath = assemblyItem.ItemSpec;
                var fileName     = Path.GetFileName(assemblyPath);
                if (MonoAndroidHelper.IsFrameworkAssembly(fileName) &&
                    !MonoAndroidHelper.FrameworkEmbeddedJarLookupTargets.Contains(fileName) &&
                    !MonoAndroidHelper.FrameworkEmbeddedNativeLibraryAssemblies.Contains(fileName))
                {
                    Log.LogDebugMessage($"Skipping framework assembly '{fileName}'.");
                    continue;
                }
                if (!File.Exists(assemblyPath))
                {
                    Log.LogDebugMessage($"Skipping non-existent dependency '{assemblyPath}'.");
                    continue;
                }
                if (bool.TryParse(assemblyItem.GetMetadata(AndroidSkipResourceExtraction), out skip) && skip)
                {
                    Log.LogDebugMessage("Skipping resource extraction for '{0}' .", assemblyPath);
                    continue;
                }
                string assemblyFileName  = Path.GetFileName(assemblyPath);
                string assemblyIdentName = assemblyMap.GetLibraryImportDirectoryNameForAssembly(assemblyFileName);
                string outDirForDll      = Path.Combine(OutputImportDirectory, assemblyIdentName);
                string importsDir        = Path.Combine(outDirForDll, ImportsDirectory);
                string nativeimportsDir  = Path.Combine(outDirForDll, NativeImportsDirectory);
                string resDir            = Path.Combine(importsDir, "res");
                string assetsDir         = Path.Combine(importsDir, "assets");

                // Skip already-extracted resources.
                bool   updated      = false;
                string assemblyHash = MonoAndroidHelper.HashFile(assemblyPath);
                string stamp        = Path.Combine(outdir, assemblyIdentName + ".stamp");
                string stampHash    = File.Exists(stamp) ? File.ReadAllText(stamp) : null;
                if (assemblyHash == stampHash)
                {
                    Log.LogDebugMessage("Skipped resource lookup for {0}: extracted files are up to date", assemblyPath);
                    if (Directory.Exists(importsDir))
                    {
                        foreach (var file in Directory.EnumerateFiles(importsDir, "*.jar", SearchOption.AllDirectories))
                        {
                            AddJar(jars, Path.GetFullPath(file));
                        }
                    }
                    if (Directory.Exists(resDir))
                    {
                        var taskItem = new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> {
                            { OriginalFile, assemblyPath },
                        });
                        if (bool.TryParse(assemblyItem.GetMetadata(AndroidSkipResourceProcessing), out skip) && skip)
                        {
                            taskItem.SetMetadata(AndroidSkipResourceProcessing, "True");
                        }
                        resolvedResourceDirectories.Add(taskItem);
                    }
                    if (Directory.Exists(assetsDir))
                    {
                        resolvedAssetDirectories.Add(new TaskItem(Path.GetFullPath(assetsDir), new Dictionary <string, string> {
                            { OriginalFile, assemblyPath }
                        }));
                    }
                    foreach (var env in Directory.EnumerateFiles(outDirForDll, "__AndroidEnvironment__*", SearchOption.TopDirectoryOnly))
                    {
                        resolvedEnvironments.Add(new TaskItem(env, new Dictionary <string, string> {
                            { OriginalFile, assemblyPath }
                        }));
                    }
                    continue;
                }

                Log.LogDebugMessage($"Refreshing {assemblyFileName}");

                using (var pe = new PEReader(File.OpenRead(assemblyPath))) {
                    var reader = pe.GetMetadataReader();
                    foreach (var handle in reader.ManifestResources)
                    {
                        var    resource = reader.GetManifestResource(handle);
                        string name     = reader.GetString(resource.Name);

                        // android environment files
                        if (name.StartsWith("__AndroidEnvironment__", StringComparison.OrdinalIgnoreCase))
                        {
                            var outFile = Path.Combine(outDirForDll, name);
                            using (var stream = pe.GetEmbeddedResourceStream(resource)) {
                                updated |= MonoAndroidHelper.CopyIfStreamChanged(stream, outFile);
                            }
                            resolvedEnvironments.Add(new TaskItem(Path.GetFullPath(outFile), new Dictionary <string, string> {
                                { OriginalFile, assemblyPath }
                            }));
                        }
                        // embedded jars (EmbeddedJar, EmbeddedReferenceJar)
                        else if (name.EndsWith(".jar", StringComparison.InvariantCultureIgnoreCase))
                        {
                            using (var stream = pe.GetEmbeddedResourceStream(resource)) {
                                AddJar(jars, importsDir, name, assemblyPath);
                                updated |= MonoAndroidHelper.CopyIfStreamChanged(stream, Path.Combine(importsDir, name));
                            }
                        }
                        // embedded native libraries
                        else if (name == "__AndroidNativeLibraries__.zip")
                        {
                            List <string> files = new List <string> ();
                            using (var stream = pe.GetEmbeddedResourceStream(resource))
                                using (var zip = Xamarin.Tools.Zip.ZipArchive.Open(stream)) {
                                    try {
                                        updated |= Files.ExtractAll(zip, nativeimportsDir, modifyCallback: (entryFullName) => {
                                            files.Add(Path.GetFullPath(Path.Combine(nativeimportsDir, entryFullName)));
                                            return(entryFullName
                                                   .Replace("native_library_imports\\", "")
                                                   .Replace("native_library_imports/", ""));
                                        }, deleteCallback: (fileToDelete) => {
                                            return(!files.Contains(fileToDelete));
                                        });
                                    } catch (PathTooLongException ex) {
                                        Log.LogCodedError("XA4303", Properties.Resources.XA4303, assemblyPath, ex);
                                        return;
                                    } catch (NotSupportedException ex) {
                                        Log.LogCodedError("XA4303", Properties.Resources.XA4303, assemblyPath, ex);
                                        return;
                                    }
                                }
                        }
                        // embedded AndroidResourceLibrary archive
                        else if (name == "__AndroidLibraryProjects__.zip")
                        {
                            // temporarily extracted directory will look like:
                            //    __library_projects__/[dllname]/[library_project_imports | jlibs]/bin
                            using (var stream = pe.GetEmbeddedResourceStream(resource))
                                using (var zip = Xamarin.Tools.Zip.ZipArchive.Open(stream)) {
                                    try {
                                        updated |= Files.ExtractAll(zip, importsDir, modifyCallback: (entryFullName) => {
                                            var path = entryFullName
                                                       .Replace("library_project_imports\\", "")
                                                       .Replace("library_project_imports/", "");
                                            if (path.EndsWith(".jar", StringComparison.OrdinalIgnoreCase))
                                            {
                                                AddJar(jars, importsDir, path, assemblyPath);
                                            }
                                            return(path);
                                        }, deleteCallback: (fileToDelete) => {
                                            return(!jars.ContainsKey(fileToDelete));
                                        });
                                    } catch (PathTooLongException ex) {
                                        Log.LogCodedError("XA4303", Properties.Resources.XA4303, assemblyPath, ex);
                                        return;
                                    } catch (NotSupportedException ex) {
                                        Log.LogCodedError("XA4303", Properties.Resources.XA4303, assemblyPath, ex);
                                        return;
                                    }
                                }

                            // We used to *copy* the resources to overwrite other resources,
                            // which resulted in missing resource issue.
                            // Here we replaced copy with use of '-S' option and made it to work.
                            if (Directory.Exists(resDir))
                            {
                                var taskItem = new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> {
                                    { OriginalFile, assemblyPath }
                                });
                                if (bool.TryParse(assemblyItem.GetMetadata(AndroidSkipResourceProcessing), out skip) && skip)
                                {
                                    taskItem.SetMetadata(AndroidSkipResourceProcessing, "True");
                                }
                                resolvedResourceDirectories.Add(taskItem);
                            }
                            if (Directory.Exists(assetsDir))
                            {
                                resolvedAssetDirectories.Add(new TaskItem(Path.GetFullPath(assetsDir), new Dictionary <string, string> {
                                    { OriginalFile, assemblyPath }
                                }));
                            }
                        }
                    }
                }

                if (Directory.Exists(importsDir))
                {
                    // Delete unknown files in the top directory of importsDir
                    foreach (var file in Directory.EnumerateFiles(importsDir, "*"))
                    {
                        var fullPath = Path.GetFullPath(file);
                        if (file.StartsWith("__AndroidEnvironment__", StringComparison.OrdinalIgnoreCase) && !resolvedEnvironments.Any(x => x.ItemSpec == fullPath))
                        {
                            Log.LogDebugMessage($"Deleting unknown AndroidEnvironment file: {Path.GetFileName (file)}");
                            File.Delete(fullPath);
                            updated = true;
                        }
                        else if (file.EndsWith(".jar", StringComparison.OrdinalIgnoreCase) && !jars.ContainsKey(fullPath))
                        {
                            Log.LogDebugMessage($"Deleting unknown jar: {Path.GetFileName (file)}");
                            File.Delete(fullPath);
                            updated = true;
                        }
                    }
                    if (assemblyHash != stampHash)
                    {
                        Log.LogDebugMessage($"Saving hash to {stamp}, changes: {updated}");
                        //NOTE: if the hash is different we always want to write the file, but preserve the timestamp if no changes
                        WriteAllText(stamp, assemblyHash, preserveTimestamp: !updated);
                    }
                }
            }
            foreach (var aarFile in AarLibraries ?? new ITaskItem[0])
            {
                if (!File.Exists(aarFile.ItemSpec))
                {
                    continue;
                }
                string aarIdentityName = Path.GetFileName(aarFile.ItemSpec);
                aarIdentityName = assemblyMap.GetLibraryImportDirectoryNameForAssembly(aarIdentityName);
                string outDirForDll = Path.Combine(OutputImportDirectory, aarIdentityName);
                string importsDir   = Path.Combine(outDirForDll, ImportsDirectory);
                string resDir       = Path.Combine(importsDir, "res");
                string assetsDir    = Path.Combine(importsDir, "assets");

                bool   updated     = false;
                string aarHash     = MonoAndroidHelper.HashFile(aarFile.ItemSpec);
                string stamp       = Path.Combine(outdir, aarIdentityName + ".stamp");
                string stampHash   = File.Exists(stamp) ? File.ReadAllText(stamp) : null;
                var    aarFullPath = Path.GetFullPath(aarFile.ItemSpec);
                if (aarHash == stampHash)
                {
                    Log.LogDebugMessage("Skipped {0}: extracted files are up to date", aarFile.ItemSpec);
                    if (Directory.Exists(importsDir))
                    {
                        foreach (var file in Directory.EnumerateFiles(importsDir, "*.jar", SearchOption.AllDirectories))
                        {
                            AddJar(jars, Path.GetFullPath(file));
                        }
                    }
                    if (Directory.Exists(resDir))
                    {
                        var skipProcessing = aarFile.GetMetadata(AndroidSkipResourceProcessing);
                        if (string.IsNullOrEmpty(skipProcessing))
                        {
                            skipProcessing = "True";
                        }
                        resolvedResourceDirectories.Add(new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> {
                            { OriginalFile, Path.GetFullPath(aarFile.ItemSpec) },
                            { AndroidSkipResourceProcessing, skipProcessing },
                        }));
                    }
                    if (Directory.Exists(assetsDir))
                    {
                        resolvedAssetDirectories.Add(new TaskItem(Path.GetFullPath(assetsDir), new Dictionary <string, string> {
                            { OriginalFile, aarFullPath },
                        }));
                    }
                    continue;
                }

                Log.LogDebugMessage($"Refreshing {aarFile.ItemSpec}");

                // temporarily extracted directory will look like:
                // _lp_/[aarFile]

                using (var zip = MonoAndroidHelper.ReadZipFile(aarFile.ItemSpec)) {
                    try {
                        updated |= Files.ExtractAll(zip, importsDir, modifyCallback: (entryFullName) => {
                            var entryFileName = Path.GetFileName(entryFullName);
                            var entryPath     = Path.GetDirectoryName(entryFullName);
                            if (entryFileName.StartsWith("internal_impl", StringComparison.InvariantCulture))
                            {
                                var hash = Files.HashString(entryFileName);
                                var jar  = Path.Combine(entryPath, $"internal_impl-{hash}.jar");
                                AddJar(jars, importsDir, jar, aarFullPath);
                                return(jar);
                            }
                            if (entryFullName.EndsWith(".jar", StringComparison.OrdinalIgnoreCase))
                            {
                                AddJar(jars, importsDir, entryFullName, aarFullPath);
                            }
                            else if (entryFullName.StartsWith(".netenv/", StringComparison.OrdinalIgnoreCase) ||
                                     entryFullName.StartsWith(".netenv\\", StringComparison.OrdinalIgnoreCase))
                            {
                                var fullPath = Path.GetFullPath(Path.Combine(importsDir, entryFullName));
                                resolvedEnvironments.Add(new TaskItem(fullPath, new Dictionary <string, string> {
                                    { OriginalFile, aarFile.ItemSpec }
                                }));
                            }
                            return(entryFullName);
                        }, deleteCallback: (fileToDelete) => {
                            return(!jars.ContainsKey(fileToDelete));
                        }, skipCallback: Files.ShouldSkipEntryInAar);

                        if (Directory.Exists(importsDir) && aarHash != stampHash)
                        {
                            Log.LogDebugMessage($"Saving hash to {stamp}, changes: {updated}");
                            //NOTE: if the hash is different we always want to write the file, but preserve the timestamp if no changes
                            WriteAllText(stamp, aarHash, preserveTimestamp: !updated);
                        }
                    } catch (PathTooLongException ex) {
                        Log.LogErrorFromException(new PathTooLongException($"Error extracting resources from \"{aarFile.ItemSpec}\"", ex));
                    }
                }
                if (Directory.Exists(resDir))
                {
                    var skipProcessing = aarFile.GetMetadata(AndroidSkipResourceProcessing);
                    if (string.IsNullOrEmpty(skipProcessing))
                    {
                        skipProcessing = "True";
                    }
                    resolvedResourceDirectories.Add(new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> {
                        { OriginalFile, aarFullPath },
                        { AndroidSkipResourceProcessing, skipProcessing },
                    }));
                }
                if (Directory.Exists(assetsDir))
                {
                    resolvedAssetDirectories.Add(new TaskItem(Path.GetFullPath(assetsDir), new Dictionary <string, string> {
                        { OriginalFile, aarFullPath },
                    }));
                }
            }
        }
        // Extracts library project contents under e.g. obj/Debug/[__library_projects__/*.jar | res/*/*]
        // Extracts library project contents under e.g. obj/Debug/[lp/*.jar | res/*/*]
        void Extract(
            DirectoryAssemblyResolver res,
            ICollection <string> jars,
            ICollection <ITaskItem> resolvedResourceDirectories,
            ICollection <string> resolvedAssetDirectories,
            ICollection <string> resolvedEnvironments)
        {
            // lets "upgrade" the old directory.
            string oldPath = Path.GetFullPath(Path.Combine(OutputImportDirectory, "..", "__library_projects__"));

            if (!OutputImportDirectory.Contains("__library_projects__") && Directory.Exists(oldPath))
            {
                MonoAndroidHelper.SetDirectoryWriteable(Path.Combine(oldPath, ".."));
                Directory.Delete(oldPath, recursive: true);
            }
            var outdir = Path.GetFullPath(OutputImportDirectory);

            Directory.CreateDirectory(outdir);

            foreach (var assembly in Assemblies)
            {
                res.Load(assembly.ItemSpec);
            }

            // FIXME: reorder references by import priority (not sure how to do that yet)
            foreach (var assemblyPath in Assemblies
                     .Select(a => GetTargetAssembly(a))
                     .Where(a => a != null)
                     .Distinct())
            {
                if (DesignTimeBuild && !File.Exists(assemblyPath))
                {
                    Log.LogDebugMessage("Skipping non existant dependancy '{0}' due to design time build.", assemblyPath);
                    continue;
                }
                string assemblyFileName  = Path.GetFileNameWithoutExtension(assemblyPath);
                string assemblyIdentName = assemblyFileName;
                if (UseShortFileNames)
                {
                    assemblyIdentName = assemblyMap.GetLibraryImportDirectoryNameForAssembly(assemblyFileName);
                }
                string outDirForDll     = Path.Combine(OutputImportDirectory, assemblyIdentName);
                string importsDir       = Path.Combine(outDirForDll, ImportsDirectory);
                string nativeimportsDir = Path.Combine(outDirForDll, NativeImportsDirectory);
#if SEPARATE_CRUNCH
                // FIXME: review these binResDir thing and enable this. Eclipse does this.
                // Enabling these blindly causes build failure on ActionBarSherlock.
                //string binResDir = Path.Combine (importsDir, "bin", "res");
                //string binAssemblyDir = Path.Combine (importsDir, "bin", "assets");
#endif
                string resDir    = Path.Combine(importsDir, "res");
                string assetsDir = Path.Combine(importsDir, "assets");

                // Skip already-extracted resources.
                bool   updated      = false;
                string assemblyHash = MonoAndroidHelper.HashFile(assemblyPath);
                string stamp        = Path.Combine(outdir, assemblyIdentName + ".stamp");
                string stampHash    = File.Exists(stamp) ? File.ReadAllText(stamp) : null;
                if (assemblyHash == stampHash)
                {
                    Log.LogDebugMessage("Skipped resource lookup for {0}: extracted files are up to date", assemblyPath);
#if SEPARATE_CRUNCH
                    // FIXME: review these binResDir/binAssemblyDir thing and enable this. Eclipse does this.
                    // Enabling these blindly causes build failure on ActionBarSherlock.
                    if (Directory.Exists(binResDir))
                    {
                        resolvedResourceDirectories.Add(binResDir);
                    }
                    if (Directory.Exists(binAssemblyDir))
                    {
                        resolvedAssetDirectories.Add(binAssemblyDir);
                    }
#endif
                    if (Directory.Exists(importsDir))
                    {
                        foreach (var file in Directory.EnumerateFiles(importsDir, "*.jar", SearchOption.AllDirectories))
                        {
                            AddJar(jars, Path.GetFullPath(file));
                        }
                    }
                    if (Directory.Exists(resDir))
                    {
                        var taskItem = new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> {
                            { OriginalFile, assemblyPath },
                        });
                        if (assembliesToSkip.Contains(assemblyFileName))
                        {
                            taskItem.SetMetadata(SkipAndroidResourceProcessing, "True");
                        }
                        resolvedResourceDirectories.Add(taskItem);
                    }
                    if (Directory.Exists(assetsDir))
                    {
                        resolvedAssetDirectories.Add(Path.GetFullPath(assetsDir));
                    }
                    foreach (var env in Directory.EnumerateFiles(outDirForDll, "__AndroidEnvironment__*", SearchOption.TopDirectoryOnly))
                    {
                        resolvedEnvironments.Add(env);
                    }
                    continue;
                }

                Log.LogDebugMessage($"Refreshing {assemblyFileName}.dll");

                var assembly = res.GetAssembly(assemblyPath);
                foreach (var mod in assembly.Modules)
                {
                    // android environment files
                    foreach (var envtxt in mod.Resources
                             .Where(r => r.Name.StartsWith("__AndroidEnvironment__", StringComparison.OrdinalIgnoreCase))
                             .Where(r => r is EmbeddedResource)
                             .Cast <EmbeddedResource> ())
                    {
                        var outFile = Path.Combine(outDirForDll, envtxt.Name);
                        using (var stream = envtxt.GetResourceStream()) {
                            updated |= MonoAndroidHelper.CopyIfStreamChanged(stream, outFile);
                        }
                        resolvedEnvironments.Add(Path.GetFullPath(outFile));
                    }

                    // embedded jars (EmbeddedJar, EmbeddedReferenceJar)
                    var resjars = mod.Resources
                                  .Where(r => r.Name.EndsWith(".jar", StringComparison.InvariantCultureIgnoreCase))
                                  .Select(r => (EmbeddedResource)r);
                    foreach (var resjar in resjars)
                    {
                        using (var stream = resjar.GetResourceStream()) {
                            AddJar(jars, importsDir, resjar.Name);
                            updated |= MonoAndroidHelper.CopyIfStreamChanged(stream, Path.Combine(importsDir, resjar.Name));
                        }
                    }

                    var libzip = mod.Resources.FirstOrDefault(r => r.Name == "__AndroidNativeLibraries__.zip") as EmbeddedResource;
                    if (libzip != null)
                    {
                        List <string> files = new List <string> ();
                        using (var stream = libzip.GetResourceStream())
                            using (var zip = Xamarin.Tools.Zip.ZipArchive.Open(stream)) {
                                try {
                                    updated |= Files.ExtractAll(zip, nativeimportsDir, modifyCallback: (entryFullName) => {
                                        files.Add(Path.GetFullPath(Path.Combine(nativeimportsDir, entryFullName)));
                                        return(entryFullName
                                               .Replace("native_library_imports\\", "")
                                               .Replace("native_library_imports/", ""));
                                    }, deleteCallback: (fileToDelete) => {
                                        return(!files.Contains(fileToDelete));
                                    });
                                } catch (PathTooLongException ex) {
                                    Log.LogCodedError("XA4303", $"Error extracting resources from \"{assemblyPath}\": {ex}");
                                    return;
                                } catch (NotSupportedException ex) {
                                    Log.LogCodedError("XA4303", $"Error extracting resources from \"{assemblyPath}\": {ex}");
                                    return;
                                }
                            }
                    }

                    // embedded AndroidResourceLibrary archive
                    var reszip = mod.Resources.FirstOrDefault(r => r.Name == "__AndroidLibraryProjects__.zip") as EmbeddedResource;
                    if (reszip != null)
                    {
                        // temporarily extracted directory will look like:
                        //    __library_projects__/[dllname]/[library_project_imports | jlibs]/bin
                        using (var stream = reszip.GetResourceStream())
                            using (var zip = Xamarin.Tools.Zip.ZipArchive.Open(stream)) {
                                try {
                                    updated |= Files.ExtractAll(zip, importsDir, modifyCallback: (entryFullName) => {
                                        var path = entryFullName
                                                   .Replace("library_project_imports\\", "")
                                                   .Replace("library_project_imports/", "");
                                        if (path.EndsWith(".jar", StringComparison.OrdinalIgnoreCase))
                                        {
                                            AddJar(jars, importsDir, path);
                                        }
                                        return(path);
                                    }, deleteCallback: (fileToDelete) => {
                                        return(!jars.Contains(fileToDelete));
                                    });
                                } catch (PathTooLongException ex) {
                                    Log.LogCodedError("XA4303", $"Error extracting resources from \"{assemblyPath}\": {ex}");
                                    return;
                                } catch (NotSupportedException ex) {
                                    Log.LogCodedError("XA4303", $"Error extracting resources from \"{assemblyPath}\": {ex}");
                                    return;
                                }
                            }

                        // We used to *copy* the resources to overwrite other resources,
                        // which resulted in missing resource issue.
                        // Here we replaced copy with use of '-S' option and made it to work.
#if SEPARATE_CRUNCH
                        // FIXME: review these binResDir/binAssemblyDir thing and enable this. Eclipse does this.
                        // Enabling these blindly causes build failure on ActionBarSherlock.
                        if (Directory.Exists(binResDir))
                        {
                            resolvedResourceDirectories.Add(binResDir);
                        }
                        if (Directory.Exists(binAssemblyDir))
                        {
                            resolvedAssetDirectories.Add(binAssemblyDir);
                        }
#endif
                        if (Directory.Exists(resDir))
                        {
                            var taskItem = new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> {
                                { OriginalFile, assemblyPath }
                            });
                            if (assembliesToSkip.Contains(assemblyFileName))
                            {
                                taskItem.SetMetadata(SkipAndroidResourceProcessing, "True");
                            }
                            resolvedResourceDirectories.Add(taskItem);
                        }
                        if (Directory.Exists(assetsDir))
                        {
                            resolvedAssetDirectories.Add(Path.GetFullPath(assetsDir));
                        }
                    }
                }

                if (Directory.Exists(importsDir))
                {
                    // Delete unknown files in the top directory of importsDir
                    foreach (var file in Directory.EnumerateFiles(importsDir, "*"))
                    {
                        var fullPath = Path.GetFullPath(file);
                        if (file.StartsWith("__AndroidEnvironment__", StringComparison.OrdinalIgnoreCase) && !resolvedEnvironments.Contains(fullPath))
                        {
                            Log.LogDebugMessage($"Deleting unknown AndroidEnvironment file: {Path.GetFileName (file)}");
                            File.Delete(fullPath);
                            updated = true;
                        }
                        else if (file.EndsWith(".jar", StringComparison.OrdinalIgnoreCase) && !jars.Contains(fullPath))
                        {
                            Log.LogDebugMessage($"Deleting unknown jar: {Path.GetFileName (file)}");
                            File.Delete(fullPath);
                            updated = true;
                        }
                    }
                    if (assemblyHash != stampHash)
                    {
                        Log.LogDebugMessage($"Saving hash to {stamp}, changes: {updated}");
                        //NOTE: if the hash is different we always want to write the file, but preserve the timestamp if no changes
                        WriteAllText(stamp, assemblyHash, preserveTimestamp: !updated);
                    }
                }
            }
            foreach (var aarFile in AarLibraries ?? new ITaskItem[0])
            {
                if (!File.Exists(aarFile.ItemSpec))
                {
                    continue;
                }
                string aarIdentityName = Path.GetFileNameWithoutExtension(aarFile.ItemSpec);
                if (UseShortFileNames)
                {
                    aarIdentityName = assemblyMap.GetLibraryImportDirectoryNameForAssembly(aarIdentityName);
                }
                string outDirForDll = Path.Combine(OutputImportDirectory, aarIdentityName);
                string importsDir   = Path.Combine(outDirForDll, ImportsDirectory);
                string resDir       = Path.Combine(importsDir, "res");
                string assetsDir    = Path.Combine(importsDir, "assets");

                bool   updated   = false;
                string aarHash   = MonoAndroidHelper.HashFile(aarFile.ItemSpec);
                string stamp     = Path.Combine(outdir, Path.GetFileNameWithoutExtension(aarFile.ItemSpec) + ".stamp");
                string stampHash = File.Exists(stamp) ? File.ReadAllText(stamp) : null;
                if (aarHash == stampHash)
                {
                    Log.LogDebugMessage("Skipped {0}: extracted files are up to date", aarFile.ItemSpec);
                    if (Directory.Exists(importsDir))
                    {
                        foreach (var file in Directory.EnumerateFiles(importsDir, "*.jar", SearchOption.AllDirectories))
                        {
                            AddJar(jars, Path.GetFullPath(file));
                        }
                    }
                    if (Directory.Exists(resDir))
                    {
                        resolvedResourceDirectories.Add(new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> {
                            { OriginalFile, Path.GetFullPath(aarFile.ItemSpec) },
                            { SkipAndroidResourceProcessing, "True" },
                        }));
                    }
                    if (Directory.Exists(assetsDir))
                    {
                        resolvedAssetDirectories.Add(Path.GetFullPath(assetsDir));
                    }
                    continue;
                }

                Log.LogDebugMessage($"Refreshing {aarFile.ItemSpec}");

                // temporarily extracted directory will look like:
                // _lp_/[aarFile]
                using (var zip = MonoAndroidHelper.ReadZipFile(aarFile.ItemSpec)) {
                    try {
                        updated |= Files.ExtractAll(zip, importsDir, modifyCallback: (entryFullName) => {
                            var entryFileName = Path.GetFileName(entryFullName);
                            var entryPath     = Path.GetDirectoryName(entryFullName);
                            if (entryFileName.StartsWith("internal_impl", StringComparison.InvariantCulture))
                            {
                                var hash = Files.HashString(entryFileName);
                                var jar  = Path.Combine(entryPath, $"internal_impl-{hash}.jar");
                                AddJar(jars, importsDir, jar);
                                return(jar);
                            }
                            if (entryFullName.EndsWith(".jar", StringComparison.OrdinalIgnoreCase))
                            {
                                AddJar(jars, importsDir, entryFullName);
                            }
                            return(entryFullName);
                        }, deleteCallback: (fileToDelete) => {
                            return(!jars.Contains(fileToDelete));
                        });

                        if (Directory.Exists(importsDir) && aarHash != stampHash)
                        {
                            Log.LogDebugMessage($"Saving hash to {stamp}, changes: {updated}");
                            //NOTE: if the hash is different we always want to write the file, but preserve the timestamp if no changes
                            WriteAllText(stamp, aarHash, preserveTimestamp: !updated);
                        }
                    } catch (PathTooLongException ex) {
                        Log.LogErrorFromException(new PathTooLongException($"Error extracting resources from \"{aarFile.ItemSpec}\"", ex));
                    }
                }
                if (Directory.Exists(resDir))
                {
                    resolvedResourceDirectories.Add(new TaskItem(Path.GetFullPath(resDir), new Dictionary <string, string> {
                        { OriginalFile, Path.GetFullPath(aarFile.ItemSpec) },
                        { SkipAndroidResourceProcessing, "True" },
                    }));
                }
                if (Directory.Exists(assetsDir))
                {
                    resolvedAssetDirectories.Add(Path.GetFullPath(assetsDir));
                }
            }
        }