public XDocument GetXML()
        {
            var sampleCSProjPath = (Type == ProjectStyle.PackageReference && ToolingVersion15) ?
                                   "Test.Utility.compiler.resources.project2.csproj" :
                                   "Test.Utility.compiler.resources.project1.csproj";

            var s   = ResourceTestUtility.GetResource(sampleCSProjPath, typeof(SimpleTestProjectContext));
            var xml = XDocument.Parse(s);

            ProjectFileUtils.AddProperties(xml, new Dictionary <string, string>()
            {
                { "ProjectGuid", "{" + ProjectGuid.ToString() + "}" },
                { "BaseIntermediateOutputPath", OutputPath },
                { "AssemblyName", ProjectName }
            });

            ProjectFileUtils.AddProperties(xml, Properties);

            if (Type == ProjectStyle.PackageReference)
            {
                if (WarningsAsErrors)
                {
                    ProjectFileUtils.AddProperties(xml, new Dictionary <string, string>()
                    {
                        { "WarningsAsErrors", "true" }
                    });
                }

                ProjectFileUtils.AddProperties(xml, new Dictionary <string, string>()
                {
                    { "Version", Version },
                    { "DebugType", "portable" }
                });

                if (!IsLegacyPackageReference)
                {
                    var tfPropName = SingleTargetFramework ? "TargetFramework" : "TargetFrameworks";

                    ProjectFileUtils.AddProperties(xml, new Dictionary <string, string>()
                    {
                        { tfPropName, OriginalFrameworkStrings.Count != 0 ? string.Join(";", OriginalFrameworkStrings):
                          string.Join(";", Frameworks.Select(f => f.Framework.GetShortFolderName())) },
                    });
                }

                var addedToAllProjectReferences = new HashSet <SimpleTestProjectContext>();
                var addedToAllPackageReferences = new HashSet <SimpleTestPackageContext>();

                foreach (var frameworkInfo in Frameworks)
                {
                    // Add properties with a TFM condition
                    ProjectFileUtils.AddProperties(xml, frameworkInfo.Properties, $" '$(TargetFramework)' == '{frameworkInfo.Framework.GetShortFolderName()}' ");

                    foreach (var package in frameworkInfo.PackageReferences)
                    {
                        var referenceFramework = frameworkInfo.Framework;

                        // Drop the conditional if it is not needed
                        if (Frameworks.All(f => f.PackageReferences.Contains(package)))
                        {
                            referenceFramework = NuGetFramework.AnyFramework;

                            if (!addedToAllPackageReferences.Add(package))
                            {
                                // Skip since this was already added
                                continue;
                            }
                        }

                        var props      = new Dictionary <string, string>();
                        var attributes = new Dictionary <string, string>();

                        if (ToolingVersion15)
                        {
                            attributes.Add("Version", package.Version.ToString());
                        }
                        else
                        {
                            props.Add("Version", package.Version.ToString());
                        }

                        if (!string.IsNullOrEmpty(package.Include))
                        {
                            props.Add("IncludeAssets", package.Include);
                        }

                        if (!string.IsNullOrEmpty(package.Exclude))
                        {
                            props.Add("ExcludeAssets", package.Exclude);
                        }

                        if (!string.IsNullOrEmpty(package.PrivateAssets))
                        {
                            props.Add("PrivateAssets", package.PrivateAssets);
                        }

                        if (!string.IsNullOrEmpty(package.NoWarn))
                        {
                            props.Add("NoWarn", package.NoWarn);
                        }

                        ProjectFileUtils.AddItem(
                            xml,
                            "PackageReference",
                            package.Id,
                            referenceFramework,
                            props,
                            attributes);
                    }

                    foreach (var project in frameworkInfo.ProjectReferences)
                    {
                        var referenceFramework = frameworkInfo.Framework;

                        // Drop the conditional if it is not needed
                        if (Frameworks.All(f => f.ProjectReferences.Contains(project)))
                        {
                            referenceFramework = NuGetFramework.AnyFramework;

                            if (!addedToAllProjectReferences.Add(project))
                            {
                                // Skip since this was already added
                                continue;
                            }
                        }

                        var props = new Dictionary <string, string>
                        {
                            { "Name", project.ProjectName },
                            { "Project", project.ProjectGuid.ToString() }
                        };

                        if (!string.IsNullOrEmpty(project.ExcludeAssets))
                        {
                            props.Add("IncludeAssets", project.ExcludeAssets);
                        }

                        if (!string.IsNullOrEmpty(project.ExcludeAssets))
                        {
                            props.Add("ExcludeAssets", project.ExcludeAssets);
                        }

                        if (!string.IsNullOrEmpty(project.PrivateAssets))
                        {
                            props.Add("PrivateAssets", project.PrivateAssets);
                        }

                        ProjectFileUtils.AddItem(
                            xml,
                            "ProjectReference",
                            $"{project.ProjectPath}",
                            referenceFramework,
                            props,
                            new Dictionary <string, string>());
                    }
                }

                // Add tool references
                foreach (var tool in DotnetCLIToolReferences)
                {
                    var props      = new Dictionary <string, string>();
                    var attributes = new Dictionary <string, string>();

                    if (ToolingVersion15)
                    {
                        attributes.Add("Version", tool.Version.ToString());
                    }
                    else
                    {
                        props.Add("Version", tool.Version.ToString());
                    }

                    ProjectFileUtils.AddItem(
                        xml,
                        "DotNetCliToolReference",
                        $"{tool.Id}",
                        NuGetFramework.AnyFramework,
                        props,
                        attributes);
                }
            }
            else
            {
                // Add all project references directly
                foreach (var project in Frameworks.SelectMany(f => f.ProjectReferences).Distinct())
                {
                    var props = new Dictionary <string, string>
                    {
                        { "Name", project.ProjectName },
                        { "Project", project.ProjectGuid.ToString() }
                    };

                    if (!string.IsNullOrEmpty(project.ExcludeAssets))
                    {
                        props.Add("IncludeAssets", project.ExcludeAssets);
                    }

                    if (!string.IsNullOrEmpty(project.ExcludeAssets))
                    {
                        props.Add("ExcludeAssets", project.ExcludeAssets);
                    }

                    if (!string.IsNullOrEmpty(project.PrivateAssets))
                    {
                        props.Add("PrivateAssets", project.PrivateAssets);
                    }

                    ProjectFileUtils.AddItem(
                        xml,
                        "ProjectReference",
                        $"{project.ProjectPath}",
                        NuGetFramework.AnyFramework,
                        props,
                        new Dictionary <string, string>());
                }
            }

            return(xml);
        }
Esempio n. 2
0
        public XDocument GetXML()
        {
            var sampleCSProjPath = (Type == ProjectStyle.PackageReference && ToolingVersion15) ?
                                   "Test.Utility.compiler.resources.project2.csproj" :
                                   "Test.Utility.compiler.resources.project1.csproj";

            var s   = ResourceTestUtility.GetResource(sampleCSProjPath, typeof(SimpleTestProjectContext));
            var xml = XDocument.Parse(s);

            //  MSBuildProjectExtensionsPath needs to be set before Microsoft.Common.props is imported, so add a new
            //  PropertyGroup as the first element under the Project
            var ns = xml.Root.GetDefaultNamespace();

            if (SetMSBuildProjectExtensionsPath)
            {
                var propertyGroup = new XElement(ns + "PropertyGroup");
                propertyGroup.Add(new XElement(ns + "MSBuildProjectExtensionsPath", OutputPath));
                xml.Root.AddFirst(propertyGroup);
            }

            ProjectFileUtils.AddProperties(xml, new Dictionary <string, string>()
            {
                { "ProjectGuid", "{" + ProjectGuid.ToString() + "}" },
                { "AssemblyName", ProjectName }
            });

            ProjectFileUtils.AddProperties(xml, Properties);

            if (Type == ProjectStyle.PackageReference)
            {
                if (WarningsAsErrors)
                {
                    ProjectFileUtils.AddProperties(xml, new Dictionary <string, string>()
                    {
                        { "WarningsAsErrors", "true" }
                    });
                }

                ProjectFileUtils.AddProperties(xml, new Dictionary <string, string>()
                {
                    { "Version", Version },
                    { "DebugType", "portable" }
                });

                if (!IsLegacyPackageReference)
                {
                    var tfPropName = SingleTargetFramework ? "TargetFramework" : "TargetFrameworks";

                    ProjectFileUtils.AddProperties(xml, new Dictionary <string, string>()
                    {
                        { tfPropName, OriginalFrameworkStrings.Count != 0 ? string.Join(";", OriginalFrameworkStrings):
                          string.Join(";", Frameworks.Select(f => f.Framework.GetShortFolderName())) },
                    });
                }

                var addedToAllProjectReferences = new HashSet <SimpleTestProjectContext>();
                var addedToAllPackageReferences = new HashSet <SimpleTestPackageContext>();
                var addedToAllPackageDownloads  = new HashSet <SimpleTestPackageContext>();

                foreach (var frameworkInfo in Frameworks)
                {
                    // Add properties with a TFM condition
                    ProjectFileUtils.AddProperties(xml, frameworkInfo.Properties, $" '$(TargetFramework)' == '{frameworkInfo.Framework.GetShortFolderName()}' ");

                    foreach (var package in frameworkInfo.PackageReferences)
                    {
                        var referenceFramework = frameworkInfo.Framework;

                        // Drop the conditional if it is not needed
                        if (Frameworks.All(f => f.PackageReferences.Contains(package)))
                        {
                            referenceFramework = NuGetFramework.AnyFramework;

                            if (!addedToAllPackageReferences.Add(package))
                            {
                                // Skip since this was already added
                                continue;
                            }
                        }

                        var props      = new Dictionary <string, string>();
                        var attributes = new Dictionary <string, string>();

                        // To support CPVM scenarios the Version can be null
                        // In these cases do not write any version
                        if (package.Version != null)
                        {
                            if (ToolingVersion15)
                            {
                                attributes.Add("Version", package.Version.ToString());
                            }
                            else
                            {
                                props.Add("Version", package.Version.ToString());
                            }
                        }

                        if (!string.IsNullOrEmpty(package.Include))
                        {
                            props.Add("IncludeAssets", package.Include);
                        }

                        if (!string.IsNullOrEmpty(package.Exclude))
                        {
                            props.Add("ExcludeAssets", package.Exclude);
                        }

                        if (!string.IsNullOrEmpty(package.PrivateAssets))
                        {
                            props.Add("PrivateAssets", package.PrivateAssets);
                        }

                        if (!string.IsNullOrEmpty(package.Aliases))
                        {
                            props.Add("Aliases", package.Aliases);
                        }

                        if (!string.IsNullOrEmpty(package.NoWarn))
                        {
                            props.Add("NoWarn", package.NoWarn);
                        }

                        ProjectFileUtils.AddItem(
                            xml,
                            "PackageReference",
                            package.Id,
                            referenceFramework,
                            props,
                            attributes);
                    }

                    foreach (var package in frameworkInfo.PackageDownloads)
                    {
                        var referenceFramework = frameworkInfo.Framework;

                        // Drop the conditional if it is not needed
                        if (Frameworks.All(f => f.PackageDownloads.Contains(package)))
                        {
                            referenceFramework = NuGetFramework.AnyFramework;

                            if (!addedToAllPackageDownloads.Add(package))
                            {
                                // Skip since this was already added
                                continue;
                            }
                        }

                        var props      = new Dictionary <string, string>();
                        var attributes = new Dictionary <string, string>();

                        props.Add("Version", $"[{package.Version.ToString()}]");

                        ProjectFileUtils.AddItem(
                            xml,
                            "PackageDownload",
                            package.Id,
                            referenceFramework,
                            props,
                            attributes);
                    }


                    foreach (var project in frameworkInfo.ProjectReferences)
                    {
                        var referenceFramework = frameworkInfo.Framework;

                        // Drop the conditional if it is not needed
                        if (Frameworks.All(f => f.ProjectReferences.Contains(project)))
                        {
                            referenceFramework = NuGetFramework.AnyFramework;

                            if (!addedToAllProjectReferences.Add(project))
                            {
                                // Skip since this was already added
                                continue;
                            }
                        }

                        var props = new Dictionary <string, string>
                        {
                            { "Name", project.ProjectName },
                            { "Project", project.ProjectGuid.ToString() }
                        };

                        if (!string.IsNullOrEmpty(project.ExcludeAssets))
                        {
                            props.Add("IncludeAssets", project.ExcludeAssets);
                        }

                        if (!string.IsNullOrEmpty(project.ExcludeAssets))
                        {
                            props.Add("ExcludeAssets", project.ExcludeAssets);
                        }

                        if (!string.IsNullOrEmpty(project.PrivateAssets))
                        {
                            props.Add("PrivateAssets", project.PrivateAssets);
                        }

                        ProjectFileUtils.AddItem(
                            xml,
                            "ProjectReference",
                            $"{project.ProjectPath}",
                            referenceFramework,
                            props,
                            new Dictionary <string, string>());
                    }
                }

                // Add tool references
                foreach (var tool in DotnetCLIToolReferences)
                {
                    var props      = new Dictionary <string, string>();
                    var attributes = new Dictionary <string, string>();

                    if (ToolingVersion15)
                    {
                        attributes.Add("Version", tool.Version.ToString());
                    }
                    else
                    {
                        props.Add("Version", tool.Version.ToString());
                    }

                    ProjectFileUtils.AddItem(
                        xml,
                        "DotNetCliToolReference",
                        $"{tool.Id}",
                        NuGetFramework.AnyFramework,
                        props,
                        attributes);
                }
            }
            else
            {
                // Add all project references directly
                foreach (var project in Frameworks.SelectMany(f => f.ProjectReferences).Distinct())
                {
                    var props = new Dictionary <string, string>
                    {
                        { "Name", project.ProjectName },
                        { "Project", project.ProjectGuid.ToString() }
                    };

                    if (!string.IsNullOrEmpty(project.ExcludeAssets))
                    {
                        props.Add("IncludeAssets", project.ExcludeAssets);
                    }

                    if (!string.IsNullOrEmpty(project.ExcludeAssets))
                    {
                        props.Add("ExcludeAssets", project.ExcludeAssets);
                    }

                    if (!string.IsNullOrEmpty(project.PrivateAssets))
                    {
                        props.Add("PrivateAssets", project.PrivateAssets);
                    }

                    ProjectFileUtils.AddItem(
                        xml,
                        "ProjectReference",
                        $"{project.ProjectPath}",
                        NuGetFramework.AnyFramework,
                        props,
                        new Dictionary <string, string>());
                }
            }

            return(xml);
        }
        public override bool Execute()
        {
            /* This process does something similar to what ScanSwiftTask and IncludeSwiftTask does
             * but with caveats.
             *
             * The goal was to have a single code base for both macOS and Windows, but due to how
             * iOS compilation works on Windows it was not possible.
             *
             * When you do iOS specific compilation steps on Windows it is not actually running on
             * Windows, it is sending a message and required files to a build server on macOS that
             * runs and copy the results back to Windows when necessary.
             *
             * This messaging from Windows to macOS happens through a class called TaskRunner
             * that lives on Xamarin.Messaging...
             *
             * My first attempt to make the compilation work on Windows was to add a copy of
             * Xamarin.Messaging (or try reflection), access the TaskRunner and send the message
             * to macOS to run IncludeSwiftTask and ScanSwiftTask, but it turns out these classes are
             * nowhere to be found on macOS build agent - are all dependencies installed when
             * Xamarin first install the build agent or is there a way to inject code on demand?
             * I assumed the former was the case and stopped persuing this route as it was too
             * hacky and I couldnt find the code for Xamarin.Messaging dlls, all I had was
             * disassembled versions of those.
             *
             * The second attempt was to convert these Tasks into commands to be executed
             * by Xamarin version of Microsoft.Build.Tasks.Exec that has all the plubling necessary
             * to make the communication between macOS and Windows work, as it is pre-installed on
             * the build agent, but it didnt work. For some reason I was getting a
             * *"Exec" task received an invalid value for the "StdOutEncoding" parameter.*
             * while trying to read the command output. What?!
             *
             * Tried to save the ouput to a file that was going to be synchronized between macOS and
             * Windows, but only the file existence was, not its content.
             *
             * End result? I can only do one-way communication and send the entire thing as a single command.
             *
             * I need to say that even though it doenst work the same as macOS it is a huge
             * improvement over the original version - more on that on another comment.
             *
             * There are only 2 missing features:
             *
             * - There is no way to figure out what are the architectures available on the
             * current version of Swift on your Xcode, so I had to include a precomputed list.
             * Search for GetPlatformName method. macOS version learns this on demand so when
             * the day comes for a new architecture to be included I dont have to do anything
             * and it will just work. Windows version will require an update.
             *
             * - When searching for Swift dependencies on the Swift runtime the script is only
             * doing a single check, so if one day it happens that your Framework depends on A
             * that depends on B that depends on C and B is not a direct dependency from any of
             * your Frameworks and only B have a reference to C. C will be missing. I should
             * draw this, because dependency explanations are a pain to understand from text.
             * macOS version continue to scan all libraries it finds until there is nothing else
             * to scan.
             *
             * Sorry Windows users, at least for now I dont know what can be done to unify things!
             */

            string args      = GetLipoArgs();
            var    xcodePath = GetRuntimePath();

            var sb = new StringBuilder();

            // Scan app Frameworks for Swift Dependencies
            sb.Append("{ ");
            sb.Append(string.Join(" & ", Frameworks.Select(c => $"otool -l {c.ItemSpec}")));
            sb.Append("; }");

            // Clean and sort the results to get single, unique Swift dependency per line
            sb.Append(@" | grep @rpath/libswift");
            sb.Append(@" | awk -Frpath/ '{print $2}' | awk -F..offset '{print $1}'"); //sb.Append(@" | sed -E 's/^.*@rpath.(.*.dylib).*$/\1/'"); // !#$!@# path translation
            sb.Append(@" | sort | uniq");

            // Swift libraries can depend on another Swift library - this only goes 1 level deep
            // need to test if it will be an issue - macOS version doesnt work the same way.
            sb.Append(@" | while read line; do ");
            sb.Append($@"otool -l {xcodePath}/$line");
            sb.Append(@" | grep @rpath/libswift");
            sb.Append(@" | awk -Frpath/ '{print $2}' | awk -F..offset '{print $1}'"); //sb.Append(@" | sed -E 's/^.*@rpath.(.*.dylib).*$/\1/'"); // !#$!@# path translation
            sb.Append(@";done");
            sb.Append(@" | sort | uniq");

            // For each dependency found copy it to the /Frameworks folder
            // Removing the architectures we dont need for the final project
            sb.Append(@" | while read line; do ");
            sb.Append($@"lipo {xcodePath}/$line {args} {GetOutputPath(string.Empty)}/$line"); // TODO: how to make it parallel?
            sb.Append(@";done");

            Command = new TaskItem(sb.ToString());

            return(true);
        }