예제 #1
0
        public override bool Execute()
        {
            LogMessage("Starting XamarinDownloadPartialZips");
            LogMessage("DestinationBase: {0}", DestinationBase);
            LogMessage("CacheDirectory: {0}", CacheDirectory);

            Task.Run(async() => {
                List <PartialZipDownload> parts = null;
                try {
                    http = new HttpClient();

                    var cacheDir      = DownloadUtils.GetCacheDir(CacheDirectory);
                    var downloadUtils = new DownloadUtils(this, cacheDir);

                    parts = downloadUtils.ParsePartialZipDownloadItems(Parts, AllowUnsecureUrls);

                    await DownloadAll(cacheDir, parts).ConfigureAwait(false);
                } catch (Exception ex) {
                    LogErrorFromException(ex);

                    // Log Custom error if one was specified in the partial download info
                    var firstPart = parts?.FirstOrDefault();
                    if (!string.IsNullOrEmpty(firstPart?.CustomErrorCode) && !string.IsNullOrEmpty(firstPart?.CustomErrorMessage))
                    {
                        LogCodedError(firstPart.CustomErrorCode, firstPart.CustomErrorMessage);
                    }
                } finally {
                    Complete();
                }
            });

            var result = base.Execute();

            return(result && !Log.HasLoggedErrors);
        }
        async Task <bool> ExtractPartAndValidate(PartialZipDownload part, Stream partInputStream, string cacheDirectory)
        {
            string fileHash = null;

            var outputPath = GetOutputPath(cacheDirectory, part);

            using (var iis = new System.IO.Compression.DeflateStream(partInputStream, System.IO.Compression.CompressionMode.Decompress))
                //using (var iis = new ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream (partInputStream, new ICSharpCode.SharpZipLib.Zip.Compression.Inflater (true)))
                using (var fs = File.Open(outputPath, FileMode.Create)) {
                    await iis.CopyToAsync(fs).ConfigureAwait(false);

                    await fs.FlushAsync().ConfigureAwait(false);

                    fs.Seek(0, SeekOrigin.Begin);
                    fileHash = DownloadUtils.HashMd5(fs);
                    LogDebugMessage("Hash of Downloaded File: {0}", fileHash);

                    fs.Close();
                }

            if (!string.IsNullOrEmpty(part.Md5) && !part.Md5.Equals(fileHash, StringComparison.InvariantCultureIgnoreCase))
            {
                // TODO: HANDLE
                LogMessage("File MD5 Hash was invalid, deleting file: {0}", part.ToFile);
                File.Delete(outputPath);
                return(false);
            }

            return(true);
        }
예제 #3
0
        public override bool Execute()
        {
            var results = new List <ITaskItem> ();

            downloadUtils = new DownloadUtils(this, CacheDirectory);

            var items = downloadUtils.ParseDownloadItems(Archives);

            if (items != null)
            {
                foreach (var item in items)
                {
                    if (downloadUtils.IsAlreadyDownloaded(item))
                    {
                        continue;
                    }
                    var taskItem = new TaskItem(item.Id);
                    taskItem.SetMetadata("Type", "Download");
                    taskItem.SetMetadata("Url", item.Url);
                    taskItem.SetMetadata("CacheFile", item.CacheFile);

                    if (!string.IsNullOrEmpty(item.Sha1))
                    {
                        taskItem.SetMetadata("Sha1", item.Sha1);
                    }

                    results.Add(taskItem);
                }
            }

            var partials = downloadUtils.ParsePartialZipDownloadItems(PartialZipDownloads);

            if (partials != null)
            {
                foreach (var partialZipDownload in partials)
                {
                    if (downloadUtils.IsAlreadyDownloaded(CacheDirectory, partialZipDownload))
                    {
                        continue;
                    }
                    var taskItem = new TaskItem(partialZipDownload.Id);
                    taskItem.SetMetadata("Type", "PartialZipDownload");
                    taskItem.SetMetadata("Url", partialZipDownload.Url);
                    taskItem.SetMetadata("RangeStart", partialZipDownload.RangeStart.ToString());
                    taskItem.SetMetadata("RangeEnd", partialZipDownload.RangeEnd.ToString());

                    if (!string.IsNullOrEmpty(partialZipDownload.Md5))
                    {
                        taskItem.SetMetadata("Md5", partialZipDownload.Md5);
                    }

                    results.Add(taskItem);
                }
            }

            ArchivesToDownload = results.ToArray();

            return(true);
        }
예제 #4
0
        public override bool Execute()
        {
            downloadUtils = new DownloadUtils(this, CacheDirectory);

            Task.Run(async() => {
                try {
                    var items = downloadUtils.ParseDownloadItems(Archives);

                    foreach (var item in items)
                    {
                        await MakeSureLibraryIsInPlace(item, Token);
                    }
                } catch (Exception ex) {
                    LogErrorFromException(ex);
                } finally {
                    Complete();
                }
            });

            var result = base.Execute();

            return(result && !Log.HasLoggedErrors);
        }
예제 #5
0
        // We intentionally won't call the base implementation in this override
        // since other tasks should handle the restores
        // This task is just responsible for extracting proguard config files
        // from the .aar input files so we are reusing the base task to help
        // track down the .aar files themselves.
        public override bool Execute()
        {
            // Get the dir to store proguard config files in
            proguardIntermediateOutputPath = Path.Combine(MergeOutputDir, "proguard");
            if (!Directory.Exists(proguardIntermediateOutputPath))
            {
                Directory.CreateDirectory(proguardIntermediateOutputPath);
            }

            var additionalFileWrites = new List <ITaskItem> ();

            // Make sure our XbdMerge directory exists
            var outputDir = MergeOutputDir;

            Directory.CreateDirectory(outputDir);

            // Get our assembly restore map
            var restoreMap = BuildRestoreMap(RestoreAssemblyResources);

            if (restoreMap == null)
            {
                return(false);
            }

            // Look through all the assemblies we would restore for
            foreach (var asm in restoreMap)
            {
                // We only want to find proguard files in .aar files referenced
                // for assemblies we actually have referenced and have them mapped to
                var       asmName = new AssemblyName(asm.Key);
                ITaskItem item    = FindMatchingAssembly(InputReferencePaths, asmName);
                if (item == null)
                {
                    if (ThrowOnMissingAssembly)
                    {
                        return(false);
                    }
                    else
                    {
                        continue;
                    }
                }

                // Use a hash for the assembly name to keep paths shorter
                var saveNameHash = DownloadUtils.HashMd5(asmName.Name)?.Substring(0, 8);

                // We keep a stamp file around to avoid reprocessing, so skip if it exists
                var stampPath = Path.Combine(outputDir, saveNameHash + ".proguard.stamp");
                if (File.Exists(stampPath))
                {
                    continue;
                }

                // Get all the mapped .aar files
                var resourceItems = asm.Value;

                // We want to increment on the hash name in case there are multiple .aar files and/or proguard config
                // files for a given assembly, so we use them all and not overwrite the same name
                var entryCount = 0;

                // In theory we could have multiple .aar files? Probably never happen...
                foreach (var resourceItem in resourceItems)
                {
                    // Full path to .aar file
                    var resourceFullPath = resourceItem.GetMetadata("FullPath");

                    using (var fileStream = File.OpenRead(resourceFullPath))
                        using (var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read)) {
                            // Look for proguard config files in the archive
                            foreach (var entry in zipArchive.Entries)
                            {
                                // Skip entries which are not proguard configs
                                if (!entry.Name.Equals("proguard.txt", StringComparison.OrdinalIgnoreCase) &&
                                    !entry.Name.Equals("proguard.cfg", StringComparison.OrdinalIgnoreCase))
                                {
                                    continue;
                                }

                                // Figure out our destination filename
                                var proguardSaveFilename = Path.Combine(proguardIntermediateOutputPath, saveNameHash + entryCount + ".txt");

                                // Add this to our file writes
                                additionalFileWrites.Add(new TaskItem(proguardSaveFilename));

                                // Save out the proguard file
                                using (var entryStream = entry.Open())
                                    using (var fs = File.Create(proguardSaveFilename)) {
                                        entryStream.CopyTo(fs);
                                        fs.Flush();
                                        fs.Close();
                                    }

                                entryCount++;
                            }
                        }
                }

                // *.proguard.stamp files are additional file writes
                File.WriteAllText(stampPath, string.Empty);
                additionalFileWrites.Add(new TaskItem(stampPath));
            }

            AdditionalFileWrites = additionalFileWrites.ToArray();

            return(true);
        }
예제 #6
0
        async Task <bool> MakeSureLibraryIsInPlace(XamarinBuildDownload xbd, CancellationToken token)
        {
            // Skip extraction if the file is already in place
            var flagFile = xbd.DestinationDir + ".unpacked";

            if (File.Exists(flagFile))
            {
                return(true);
            }

            try {
                Directory.CreateDirectory(xbd.DestinationDir);
            } catch (Exception ex) {
                LogCodedError(ThisOrThat(xbd.CustomErrorCode, ErrorCodes.DirectoryCreateFailed), ThisOrThat(xbd.CustomErrorMessage, () => string.Format("Failed to create directory '{0}'.", xbd.DestinationDir)));
                LogMessage("Directory creation failure reason: " + ex.ToString(), MessageImportance.High);
                return(false);
            }

            var lockFile = xbd.DestinationDir + ".locked";

            using (var lockStream = DownloadUtils.ObtainExclusiveFileLock(lockFile, Token, TimeSpan.FromSeconds(xbd.ExclusiveLockTimeout), this)) {
                if (lockStream == null)
                {
                    LogCodedError(ErrorCodes.ExclusiveLockTimeout, "Timed out waiting for exclusive file lock on: {0}", lockFile);
                    LogMessage("Timed out waiting for an exclusive file lock on: " + lockFile, MessageImportance.High);
                    return(false);
                }

                if (!File.Exists(xbd.CacheFile) || !IsValidDownload(xbd.DestinationDir + ".sha1", xbd.CacheFile, xbd.Sha1))
                {
                    try {
                        int progress = -1;
                        DownloadProgressChangedEventHandler downloadHandler = (o, e) => {
                            if (e.ProgressPercentage % 10 != 0 || progress == e.ProgressPercentage)
                            {
                                return;
                            }
                            progress = e.ProgressPercentage;
                            LogMessage(
                                "\t({0}/{1}b), total {2:F1}%", e.BytesReceived, e.TotalBytesToReceive, e.ProgressPercentage
                                );
                        };
                        using (var client = new WebClient()) {
                            client.DownloadProgressChanged += downloadHandler;
                            LogMessage("  Downloading {0} to {1}", xbd.Url, xbd.CacheFile);
                            client.DownloadFileTaskAsync(xbd.Url, xbd.CacheFile).Wait(token);
                            LogMessage("  Downloading Complete");
                            client.DownloadProgressChanged -= downloadHandler;
                        }
                    } catch (Exception e) {
                        LogCodedError(ThisOrThat(xbd.CustomErrorCode, ErrorCodes.DownloadFailed), ThisOrThat(xbd.CustomErrorMessage, () => string.Format("Download failed. Please download {0} to a file called {1}.", xbd.Url, xbd.CacheFile)));
                        LogMessage("Download failure reason: " + e.GetBaseException().Message, MessageImportance.High);
                        File.Delete(xbd.CacheFile);
                        return(false);
                    }
                }

                if (!File.Exists(xbd.CacheFile))
                {
                    LogCodedError(ThisOrThat(xbd.CustomErrorCode, ErrorCodes.DownloadedFileMissing), ThisOrThat(xbd.CustomErrorMessage, () => string.Format("Downloaded file '{0}' is missing.", xbd.CacheFile)));
                    return(false);
                }

                if (xbd.Kind == ArchiveKind.Uncompressed)
                {
                    var uncompressedCacheFile = xbd.CacheFile;
                    if (!string.IsNullOrEmpty(xbd.ToFile))
                    {
                        uncompressedCacheFile = xbd.ToFile;
                    }

                    File.Move(xbd.CacheFile, Path.Combine(xbd.DestinationDir, Path.GetFileName(uncompressedCacheFile)));
                    File.WriteAllText(flagFile, "This marks that the extraction completed successfully");
                    return(true);
                }
                else
                {
                    if (await ExtractArchive(xbd, flagFile, token))
                    {
                        File.WriteAllText(flagFile, "This marks that the extraction completed successfully");
                        return(true);
                    }
                }
            }

            // We will attempt to delete the lock file when we're done
            try {
                if (File.Exists(lockFile))
                {
                    File.Delete(lockFile);
                }
            } catch { }

            return(false);
        }
        public List <PartialZipDownload> ParsePartialZipDownloadItems(ITaskItem [] items, bool allowUnsecureUrls)
        {
            if (items == null || items.Length <= 0)
            {
                return(new List <PartialZipDownload> ());
            }

            var result = new List <PartialZipDownload> ();

            foreach (var part in items)
            {
                var id = part.ItemSpec;
                if (!DownloadUtils.ValidateId(id))
                {
                    Log.LogCodedError(ErrorCodes.XbdInvalidItemId, "Invalid item ID {0}", id);
                    continue;
                }

                var toFile = part.GetMetadata("ToFile");
                if (string.IsNullOrEmpty(toFile))
                {
                    Log.LogCodedError(ErrorCodes.XbdInvalidToFile, "Invalid or missing required ToFile metadata on item {0}", id);
                    continue;
                }
                var url = part.GetMetadata("Url");
                if (string.IsNullOrEmpty(url))
                {
                    Log.LogCodedError(ErrorCodes.XbdInvalidUrl, "Missing required Url metadata on item {0}", id);
                    continue;
                }
                if (!EnsureSecureUrl(part, url, allowUnsecureUrls))
                {
                    continue;
                }

                var sha256 = part.GetMetadata("Sha256");

                long rangeStart = -1L;
                long.TryParse(part.GetMetadata("RangeStart"), out rangeStart);
                if (rangeStart < 0)
                {
                    Log.LogCodedError(ErrorCodes.XbdInvalidRangeStart, "Invalid or Missing required RangeStart metadata on item {0}", id);
                    continue;
                }

                long rangeEnd = -1L;
                long.TryParse(part.GetMetadata("RangeEnd"), out rangeEnd);
                if (rangeEnd < 0)
                {
                    Log.LogCodedError(ErrorCodes.XbdInvalidRangeEnd, "Invalid or Missing required RangeEnd metadata on item {0}", id);
                    continue;
                }

                if (rangeEnd <= rangeStart)
                {
                    Log.LogCodedError(ErrorCodes.XbdInvalidRange, "Invalid RangeStart and RangeEnd values, RangeEnd cannot be less than or equal to RangeStart, on item {0}", id);
                    continue;
                }

                var customErrorMsg  = part.GetMetadata("CustomErrorMessage");
                var customErrorCode = part.GetMetadata("CustomErrorCode");

                result.Add(new PartialZipDownload {
                    Id                 = id,
                    ToFile             = toFile,
                    Url                = url,
                    Sha256             = sha256,
                    RangeStart         = rangeStart,
                    RangeEnd           = rangeEnd,
                    CustomErrorMessage = customErrorMsg,
                    CustomErrorCode    = customErrorCode,
                });
            }

            // Deduplicate multiple id's of the same value
            var uniqueParts = result.GroupBy(p => p.Id).Select(kvp => kvp.FirstOrDefault());

            return(uniqueParts.ToList());
        }
예제 #8
0
        async Task DownloadAll(string cacheDirectory, List <PartialZipDownload> parts)
        {
            // Get the parts all grouped by their URL so we can batch requests with multiple ranges
            // instead of making a request to the same url for each part
            // also only grab the parts that don't already locally exist
            var uniqueUrls = parts
                             .Where(p => !File.Exists(Path.Combine(cacheDirectory, p.Id, p.ToFile)))
                             .GroupBy(p => p.Url);

            // For each unique url...
            foreach (var partsByUrl in uniqueUrls)
            {
                var downloadUrl = partsByUrl.Key;

                LogMessage("Downloading Partial Zip parts from: " + downloadUrl);

                try {
                    // Create a lock file based on the hash of the URL we are downloading from
                    // Since we could download a multipart request, we are locking on the url from any other process downloading from it
                    var lockFile = Path.Combine(cacheDirectory, DownloadUtils.Crc64(downloadUrl) + ".locked");

                    using (var lockStream = DownloadUtils.ObtainExclusiveFileLock(lockFile, base.Token, TimeSpan.FromSeconds(30))) {
                        if (lockStream == null)
                        {
                            LogCodedError(ErrorCodes.ExclusiveLockTimeout, "Timed out waiting for exclusive file lock on: {0}", lockFile);
                            LogMessage("Timed out waiting for an exclusive file lock on: " + lockFile, MessageImportance.High);

                            // Log Custom error if one was specified in the partial download info
                            var firstPart = partsByUrl.FirstOrDefault();
                            if (!string.IsNullOrEmpty(firstPart?.CustomErrorCode) && !string.IsNullOrEmpty(firstPart?.CustomErrorMessage))
                            {
                                LogCodedError(firstPart.CustomErrorCode, firstPart.CustomErrorMessage);
                            }

                            return;
                        }

                        try {
                            await Download(cacheDirectory, partsByUrl.Key, partsByUrl.ToList()).ConfigureAwait(false);
                        } catch (Exception ex) {
                            LogCodedError(ErrorCodes.PartialDownloadFailed, "Partial Download Failed for one or more parts");
                            LogErrorFromException(ex);

                            // Log Custom error if one was specified in the partial download info
                            var firstPart = partsByUrl.FirstOrDefault();
                            if (!string.IsNullOrEmpty(firstPart?.CustomErrorCode) && !string.IsNullOrEmpty(firstPart?.CustomErrorMessage))
                            {
                                LogCodedError(firstPart.CustomErrorCode, firstPart.CustomErrorMessage);
                            }
                        }
                    }

                    try {
                        if (File.Exists(lockFile))
                        {
                            File.Delete(lockFile);
                        }
                    } catch { }
                } catch (Exception ex) {
                    LogCodedError(ErrorCodes.PartialDownloadFailed, "Partial Download Failed for one or more parts");
                    LogErrorFromException(ex);

                    // Log Custom error if one was specified in the partial download info
                    var firstPart = partsByUrl.FirstOrDefault();
                    if (!string.IsNullOrEmpty(firstPart?.CustomErrorCode) && !string.IsNullOrEmpty(firstPart?.CustomErrorMessage))
                    {
                        LogCodedError(firstPart.CustomErrorCode, firstPart.CustomErrorMessage);
                    }
                }
            }
        }
        protected override Stream LoadResource(string resourceFullPath, string assemblyName)
        {
            const string AAR_DIR_PREFIX = "library_project_imports";

            var memoryStream = new MemoryStream();

            using (var fileStream = base.LoadResource(resourceFullPath, assemblyName))
                fileStream.CopyTo(memoryStream);

            using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Update, true)) {
                var entryNames = zipArchive.Entries.Select(zae => zae.FullName).ToList();

                Log.LogMessage("Found {0} entries in {1}", entryNames.Count, resourceFullPath);

                foreach (var entryName in entryNames)
                {
                    // Calculate the new name with the aar directory prefix
                    var newName = entryName;
                    if (!entryName.StartsWith(AAR_DIR_PREFIX, StringComparison.InvariantCulture))
                    {
                        newName = AAR_DIR_PREFIX + Path.DirectorySeparatorChar + newName;
                    }

                    // Open the old entry
                    var oldEntry = zipArchive.GetEntry(entryName);

                    // We are only re-adding non empty folders, otherwise we end up with a corrupt zip in mono
                    if (!string.IsNullOrEmpty(oldEntry.Name))
                    {
                        // SPOILER ALERT: UGLY WORKAROUND
                        // In the Android Support libraries, there exist multiple .aar files which have a `libs/internal_impl-25.0.0` file.
                        // In Xamarin.Android, there is a Task "CheckDuplicateJavaLibraries" which inspects jar files being pulled in from .aar files
                        // in assemblies to see if there exist any files with the same name but different content, and will throw an error if it finds any.
                        // However, for us, it is perfectly valid to have this scenario and we should not see an error.
                        // We are working around this by detecting files named like this, and renaming them to some unique value
                        // in this case, a part of the hash of the assembly name.
                        var newFile = Path.GetFileName(newName);
                        var newDir  = Path.GetDirectoryName(newName);

                        if (newFile.StartsWith("internal_impl", StringComparison.InvariantCulture))
                        {
                            newName = Path.Combine(newDir, "internal_impl-" + DownloadUtils.HashSha1(assemblyName).Substring(0, 6) + ".jar");
                        }

                        Log.LogMessage("Renaming: {0} to {1}", entryName, newName);

                        // Create a new entry based on our new name
                        var newEntry = zipArchive.CreateEntry(newName);

                        // Copy file contents over if they exist
                        if (oldEntry.Length > 0)
                        {
                            using (var oldStream = oldEntry.Open())
                                using (var newStream = newEntry.Open()) {
                                    oldStream.CopyTo(newStream);
                                }
                        }
                    }

                    // Delete the old entry regardless of if it's a folder or not
                    oldEntry.Delete();
                }
            }

            memoryStream.Position = 0;
            return(memoryStream);
        }
        internal static void FixupAar(string filename, bool androidFixManifests, TaskLoggingHelper loggingHelper)
        {
            using (var fileStream = new FileStream(filename, FileMode.Open))
                using (var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Update, true))
                {
                    var entryNames = zipArchive.Entries.Select(zae => zae.FullName).ToList();

                    loggingHelper.LogMessage("Found {0} entries in {1}", entryNames.Count, filename);

                    foreach (var entryName in entryNames)
                    {
                        var newName = entryName;

                        // Open the old entry
                        var oldEntry = zipArchive.GetEntry(entryName);

                        // Some .aars contain an AndroidManifest.xml in the aapt folder which is essentially a duplicate of the main one
                        // but a sanitized version with placeholders like ${applicationId} being escaped to _dollar blah
                        // we don't care about these for xamarin.android, which picks up both manifests and merges both
                        // This will ensure the 'sanitized' version doesn't get packaged
                        if (entryName.TrimStart('/').Equals("aapt/AndroidManifest.xml", StringComparison.InvariantCultureIgnoreCase))
                        {
                            loggingHelper.LogMessage("Found aapt/AndroidManifest.xml, skipping...");
                            // Delete the entry entirely and continue
                            oldEntry.Delete();
                            continue;
                        }

                        // We are only re-adding non empty folders, otherwise we end up with a corrupt zip in mono
                        if (!string.IsNullOrEmpty(oldEntry.Name))
                        {
                            // SPOILER ALERT: UGLY WORKAROUND
                            // In the Android Support libraries, there exist multiple .aar files which have a `libs/internal_impl-25.0.0` file.
                            // In Xamarin.Android, there is a Task "CheckDuplicateJavaLibraries" which inspects jar files being pulled in from .aar files
                            // in assemblies to see if there exist any files with the same name but different content, and will throw an error if it finds any.
                            // However, for us, it is perfectly valid to have this scenario and we should not see an error.
                            // We are working around this by detecting files named like this, and renaming them to some unique value
                            // in this case, a part of the hash of the assembly name.
                            var newFile = Path.GetFileName(newName);
                            var newDir  = Path.GetDirectoryName(newName);

                            if (newFile.StartsWith("internal_impl", StringComparison.InvariantCulture))
                            {
                                newName = Path.Combine(newDir, "internal_impl-" + DownloadUtils.Crc64(filename).Substring(0, 6) + ".jar");
                            }

                            loggingHelper.LogMessage("Renaming: {0} to {1}", entryName, newName);

                            // Create a new entry based on our new name
                            var newEntry = zipArchive.CreateEntry(newName);

                            // Since Xamarin.Android's AndoridManifest.xml merging code is not as sophisticated as gradle's yet, we may need
                            // to fix some things up in the manifest file to get it to merge properly into our applications
                            // Here we will check to see if Fixing manifests was enabled, and if the entry we are on is the AndroidManifest.xml file
                            if (androidFixManifests && oldEntry.Length > 0 && newName.EndsWith("AndroidManifest.xml", StringComparison.OrdinalIgnoreCase))
                            {
                                // android: namespace
                                XNamespace xns = "http://schemas.android.com/apk/res/android";

                                using (var oldStream = oldEntry.Open())
                                    using (var xmlReader = System.Xml.XmlReader.Create(oldStream))
                                    {
                                        var xdoc = XDocument.Load(xmlReader);

                                        // BEGIN FIXUP #1
                                        // Some `android:name` attributes will start with a . indicating, that the `package` value of the `manifest` element
                                        // should be prefixed dynamically/at merge to this attribute value.  Xamarin.Android doesn't handle this case yet
                                        // so we are going to manually take care of it.

                                        // Get the package name from the manifest node
                                        var packageName = xdoc.Document.Descendants("manifest")?.FirstOrDefault()?.Attribute("package")?.Value;

                                        if (!string.IsNullOrEmpty(packageName))
                                        {
                                            // Find all elements in the xml document that have a `android:name` attribute which starts with a .
                                            // Select all of them, and then change the `android:name` attribute value to be the
                                            // package name we found in the `manifest` element previously + the original attribute value
                                            xdoc.Document.Descendants()
                                            .Where(elem => elem.Attribute(xns + "name")?.Value?.StartsWith(".", StringComparison.Ordinal) ?? false)
                                            .ToList()
                                            .ForEach(elem => elem.SetAttributeValue(xns + "name", packageName + elem.Attribute(xns + "name").Value));
                                        }
                                        // END FIXUP #1

                                        using (var newStream = newEntry.Open())
                                            using (var xmlWriter = System.Xml.XmlWriter.Create(newStream))
                                            {
                                                xdoc.WriteTo(xmlWriter);
                                            }
                                    }
                            }
                            else
                            {
                                // Copy file contents over if they exist
                                if (oldEntry.Length > 0)
                                {
                                    using (var oldStream = oldEntry.Open())
                                        using (var newStream = newEntry.Open())
                                        {
                                            oldStream.CopyTo(newStream);
                                        }
                                }
                            }
                        }

                        // Delete the old entry regardless of if it's a folder or not
                        oldEntry.Delete();
                    }
                }
        }
예제 #11
0
        protected override Stream LoadResource(string resourceFullPath, string assemblyName)
        {
            const string AAR_DIR_PREFIX = "library_project_imports";

            var memoryStream = new MemoryStream();

            using (var fileStream = base.LoadResource(resourceFullPath, assemblyName))
                fileStream.CopyTo(memoryStream);

            using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Update, true)) {
                var entryNames = zipArchive.Entries.Select(zae => zae.FullName).ToList();

                Log.LogMessage("Found {0} entries in {1}", entryNames.Count, resourceFullPath);

                foreach (var entryName in entryNames)
                {
                    // Calculate the new name with the aar directory prefix
                    var newName = entryName;
                    if (!entryName.StartsWith(AAR_DIR_PREFIX, StringComparison.InvariantCulture))
                    {
                        newName = AAR_DIR_PREFIX + "/" + newName;
                    }

                    // Open the old entry
                    var oldEntry = zipArchive.GetEntry(entryName);

                    // We are only re-adding non empty folders, otherwise we end up with a corrupt zip in mono
                    if (!string.IsNullOrEmpty(oldEntry.Name))
                    {
                        // SPOILER ALERT: UGLY WORKAROUND
                        // In the Android Support libraries, there exist multiple .aar files which have a `libs/internal_impl-25.0.0` file.
                        // In Xamarin.Android, there is a Task "CheckDuplicateJavaLibraries" which inspects jar files being pulled in from .aar files
                        // in assemblies to see if there exist any files with the same name but different content, and will throw an error if it finds any.
                        // However, for us, it is perfectly valid to have this scenario and we should not see an error.
                        // We are working around this by detecting files named like this, and renaming them to some unique value
                        // in this case, a part of the hash of the assembly name.
                        var newFile = Path.GetFileName(newName);
                        var newDir  = Path.GetDirectoryName(newName);

                        if (newFile.StartsWith("internal_impl", StringComparison.InvariantCulture))
                        {
                            newName = Path.Combine(newDir, "internal_impl-" + DownloadUtils.HashSha1(assemblyName).Substring(0, 6) + ".jar");
                        }

                        Log.LogMessage("Renaming: {0} to {1}", entryName, newName);

                        // Create a new entry based on our new name
                        var newEntry = zipArchive.CreateEntry(newName);

                        // Since Xamarin.Android's AndoridManifest.xml merging code is not as sophisticated as gradle's yet, we may need
                        // to fix some things up in the manifest file to get it to merge properly into our applications
                        // Here we will check to see if Fixing manifests was enabled, and if the entry we are on is the AndroidManifest.xml file
                        if (FixAndroidManifests && oldEntry.Length > 0 && newName.EndsWith("AndroidManifest.xml", StringComparison.OrdinalIgnoreCase))
                        {
                            // android: namespace
                            XNamespace xns = "http://schemas.android.com/apk/res/android";

                            using (var oldStream = oldEntry.Open())
                                using (var xmlReader = System.Xml.XmlReader.Create(oldStream)) {
                                    var xdoc = XDocument.Load(xmlReader);

                                    // BEGIN FIXUP #1
                                    // Some `android:name` attributes will start with a . indicating, that the `package` value of the `manifest` element
                                    // should be prefixed dynamically/at merge to this attribute value.  Xamarin.Android doesn't handle this case yet
                                    // so we are going to manually take care of it.

                                    // Get the package name from the manifest node
                                    var packageName = xdoc.Document.Descendants("manifest")?.FirstOrDefault()?.Attribute("package")?.Value;

                                    if (!string.IsNullOrEmpty(packageName))
                                    {
                                        // Find all elements in the xml document that have a `android:name` attribute which starts with a .
                                        // Select all of them, and then change the `android:name` attribute value to be the
                                        // package name we found in the `manifest` element previously + the original attribute value
                                        xdoc.Document.Descendants()
                                        .Where(elem => elem.Attribute(xns + "name")?.Value?.StartsWith(".", StringComparison.Ordinal) ?? false)
                                        .ToList()
                                        .ForEach(elem => elem.SetAttributeValue(xns + "name", packageName + elem.Attribute(xns + "name").Value));
                                    }
                                    // END FIXUP #1

                                    using (var newStream = newEntry.Open())
                                        using (var xmlWriter = System.Xml.XmlWriter.Create(newStream)) {
                                            xdoc.WriteTo(xmlWriter);
                                        }
                                }
                        }
                        else
                        {
                            // Copy file contents over if they exist
                            if (oldEntry.Length > 0)
                            {
                                using (var oldStream = oldEntry.Open())
                                    using (var newStream = newEntry.Open()) {
                                        oldStream.CopyTo(newStream);
                                    }
                            }
                        }
                    }

                    // Delete the old entry regardless of if it's a folder or not
                    oldEntry.Delete();
                }
            }

            memoryStream.Position = 0;
            return(memoryStream);
        }