private void AutomaticallyPackageNormalProject( DefinitionInfo[] definitions, List <Service> services, FileFilter fileFilter, string rootPath, string platform, DefinitionInfo definition, List <string> temporaryFiles) { var document = XDocument.Load(definition.DefinitionPath); var externalProjectDocument = new XmlDocument(); externalProjectDocument.AppendChild(externalProjectDocument.CreateXmlDeclaration("1.0", "UTF-8", null)); var externalProject = externalProjectDocument.CreateElement("ExternalProject"); externalProjectDocument.AppendChild(externalProject); externalProject.SetAttribute("Name", definition.Name); if (definition.PostBuildHook) { externalProject.SetAttribute("PostBuildHook", "True"); } var externalProjectServices = externalProjectDocument.CreateElement("Services"); externalProject.AppendChild(externalProjectServices); // Just import all declared services as available, regardless of conflicts or // requirements. We don't have a clean way of automatically translating // services for packages (it has to be done manually because services can // change the resulting code that's built). So that things work 95% of time, // just declare all services available for projects included via the automatic // packaging mechanism. var servicesToDeclare = new List <string>(); var servicesDeclared = document.Root.Element(XName.Get("Services")); if (servicesDeclared != null) { foreach (var serviceElement in servicesDeclared.Elements().Where(x => x.Name.LocalName == "Service")) { servicesToDeclare.Add(serviceElement.Attribute(XName.Get("Name")).Value); } } foreach (var serviceToDeclare in servicesToDeclare) { var serviceElem = externalProjectDocument.CreateElement("Service"); serviceElem.SetAttribute("Name", serviceToDeclare); var defaultForRoot = externalProjectDocument.CreateElement("DefaultForRoot"); defaultForRoot.InnerText = "True"; serviceElem.AppendChild(defaultForRoot); externalProjectServices.AppendChild(serviceElem); } var pathPrefix = this.m_ProjectOutputPathCalculator.GetProjectOutputPathPrefix(platform, definition, document, true); var assemblyName = this.m_ProjectOutputPathCalculator.GetProjectAssemblyName(platform, definition, document); var outputMode = this.m_ProjectOutputPathCalculator.GetProjectOutputMode(document); var assemblyFilesToCopy = new[] { assemblyName + ".exe", assemblyName + ".dll", assemblyName + ".dll.config", assemblyName + ".dll.mdb", assemblyName + ".pdb", assemblyName + ".xml", }; // Copy the assembly itself out to the package. switch (outputMode) { case OutputPathMode.BinConfiguration: { // In this configuration, we only ship the binaries for // the default architecture (because that's all we know // about). We also have to assume the binary folder // contains binaries for the desired platform. if (definition.Type == "Library") { // For libraries, we only copy the assembly (and immediately related files) // into it's directory. Native binaries will be expressed through the ExternalProject. foreach (var assemblyFile in assemblyFilesToCopy) { var includeMatch = fileFilter.ApplyInclude("^" + pathPrefix + Regex.Escape(assemblyFile) + "$"); var rewriteMatch = fileFilter.ApplyRewrite("^" + pathPrefix + Regex.Escape(assemblyFile) + "$", definition.Name + "/AnyCPU/" + assemblyFile); if (includeMatch && rewriteMatch) { if (assemblyFile.EndsWith(".dll")) { var binaryEntry = externalProjectDocument.CreateElement("Binary"); binaryEntry.SetAttribute("Name", assemblyFile.Substring(0, assemblyFile.Length - 4)); binaryEntry.SetAttribute("Path", definition.Name + "\\AnyCPU\\" + assemblyFile); externalProject.AppendChild(binaryEntry); } else if (assemblyFile.EndsWith(".dll.config")) { var configEntry = externalProjectDocument.CreateElement("NativeBinary"); configEntry.SetAttribute("Path", definition.Name + "\\AnyCPU\\" + assemblyFile); externalProject.AppendChild(configEntry); } } else if (includeMatch || rewriteMatch) { throw new InvalidOperationException("Automatic filter; only one rule matched."); } } } else { // For executables, we ship everything in the output directory, because we // want the executables to be able to run from the package directory. fileFilter.ApplyInclude("^" + pathPrefix + "(.+)$"); fileFilter.ApplyRewrite("^" + pathPrefix + "(.+)$", definition.Name + "/AnyCPU/$2"); // Mark the executable files in the directory as tools that can be executed. foreach (var assemblyFile in assemblyFilesToCopy) { if (assemblyFile.EndsWith(".exe")) { var binaryEntry = externalProjectDocument.CreateElement("Tool"); binaryEntry.SetAttribute("Name", assemblyFile.Substring(0, assemblyFile.Length - 4)); binaryEntry.SetAttribute("Path", definition.Name + "\\AnyCPU\\" + assemblyFile); externalProject.AppendChild(binaryEntry); } } } break; } case OutputPathMode.BinPlatformArchConfiguration: { // In this configuration, we ship binaries for AnyCPU, iPhoneSimulator or all .NET architectures // depending on whether or not the platform produces multiple architectures. On Mono, // we can't use $(Platform) within a reference's path, so we have to keep this path static // for Mono platforms. string pathArchMatch, pathArchReplace, pathArchRuntime; switch (platform.ToLowerInvariant()) { case "ios": { pathArchMatch = "iPhoneSimulator"; pathArchReplace = "iPhoneSimulator"; pathArchRuntime = "iPhoneSimulator"; break; } case "windowsphone": { pathArchMatch = "([^/]+)"; pathArchReplace = "$1"; pathArchRuntime = "$(Platform)"; break; } default: { pathArchMatch = "AnyCPU"; pathArchReplace = "AnyCPU"; pathArchRuntime = "AnyCPU"; break; } } if (definition.Type == "Library") { // For libraries, we only copy the assembly (and immediately related files) // into it's directory. Native binaries will be expressed through the ExternalProject. foreach (var assemblyFile in assemblyFilesToCopy) { var includeMatch = fileFilter.ApplyInclude("^" + pathPrefix + Regex.Escape(assemblyFile) + "$"); var rewriteMatch = fileFilter.ApplyRewrite("^" + pathPrefix + Regex.Escape(assemblyFile) + "$", definition.Name + "/" + pathArchReplace + "/" + assemblyFile); if (includeMatch && rewriteMatch) { if (assemblyFile.EndsWith(".dll")) { var binaryEntry = externalProjectDocument.CreateElement("Binary"); binaryEntry.SetAttribute("Name", assemblyFile.Substring(0, assemblyFile.Length - 4)); binaryEntry.SetAttribute("Path", definition.Name + "\\" + pathArchRuntime + "\\" + assemblyFile); externalProject.AppendChild(binaryEntry); } else if (assemblyFile.EndsWith(".dll.config")) { var configEntry = externalProjectDocument.CreateElement("NativeBinary"); configEntry.SetAttribute("Path", definition.Name + "\\" + pathArchRuntime + "\\" + assemblyFile); externalProject.AppendChild(configEntry); } } else if (includeMatch || rewriteMatch) { throw new InvalidOperationException("Automatic filter; only one rule matched."); } } } else { // For executables, we ship everything in the output directory, because we // want the executables to be able to run from the package directory. fileFilter.ApplyInclude("^" + pathPrefix + "(.+)$"); if (pathArchMatch == "([^/]+)") { fileFilter.ApplyRewrite("^" + pathPrefix + "(.+)$", definition.Name + "/" + pathArchReplace + "/$3"); } else { fileFilter.ApplyRewrite("^" + pathPrefix + "(.+)$", definition.Name + "/" + pathArchReplace + "/$2"); } // Mark the executable files in the directory as tools that can be executed. foreach (var assemblyFile in assemblyFilesToCopy) { if (assemblyFile.EndsWith(".exe")) { var binaryEntry = externalProjectDocument.CreateElement("Tool"); binaryEntry.SetAttribute("Name", assemblyFile.Substring(0, assemblyFile.Length - 4)); binaryEntry.SetAttribute("Path", definition.Name + "\\" + pathArchRuntime + "\\" + assemblyFile); externalProject.AppendChild(binaryEntry); } } } break; } case OutputPathMode.BinProjectPlatformArchConfiguration: { throw new NotSupportedException(); break; } } // Convert all of the known references into references within the external project. var definitionsByName = definitions.ToDictionarySafe( k => k.Name, v => v, (dict, x) => { var existing = dict[x.Name]; var tried = x; RedirectableConsole.WriteLine("WARNING: There is more than one project with the name " + x.Name + " (first project loaded from " + tried.AbsolutePath + ", " + "skipped loading second project from " + existing.AbsolutePath + ")"); }); foreach (var reference in document.XPathSelectElements("/Project/References/Reference")) { var includeAttribute = reference.Attribute(XName.Get("Include")); if (includeAttribute != null) { if (definitionsByName.ContainsKey(includeAttribute.Value)) { var targetDefinition = definitionsByName[includeAttribute.Value]; // If the targeted reference is an include project, skip it. if (targetDefinition == null || targetDefinition.Type != "Include") { // This reference will be converted to an external project, // so add a reference to it (in case it contains native binaries // which need to be copied out). var referenceEntry = externalProjectDocument.CreateElement("Reference"); referenceEntry.SetAttribute("Include", includeAttribute.Value); externalProject.AppendChild(referenceEntry); } } } } // Copy out any files that are marked with copy-on-build flag. var detector = new PlatformAndServiceActiveDetection(); var xmlDocument = new XmlDocument(); xmlDocument.Load(definition.DefinitionPath); var servicesInput = this.m_ServiceInputGenerator.Generate(xmlDocument, definition.Name, services); var activeServicesElement = servicesInput.ChildNodes.OfType <XmlElement>().FirstOrDefault(x => x.LocalName == "ActiveServicesNames"); var activeServices = activeServicesElement.InnerText; foreach (var file in document.XPathSelectElements("/Project/Files/*")) { var copyOnBuild = file.XPathSelectElement("CopyToOutputDirectory"); if (copyOnBuild == null) { continue; } if (copyOnBuild.Value != "PreserveNewest" && copyOnBuild.Value != "Always") { continue; } var platformsElement = file.XPathSelectElement("Platforms"); var includePlatformsElement = file.XPathSelectElement("IncludePlatforms"); var excludePlatformsElement = file.XPathSelectElement("ExcludePlatforms"); var servicesElement = file.XPathSelectElement("Services"); var includeServicesElement = file.XPathSelectElement("IncludeServices"); var excludeServicesElement = file.XPathSelectElement("ExcludeServices"); var platformsString = platformsElement != null ? platformsElement.Value : string.Empty; var includePlatformsString = includePlatformsElement != null ? includePlatformsElement.Value : string.Empty; var excludePlatformsString = excludePlatformsElement != null ? excludePlatformsElement.Value : string.Empty; var servicesString = servicesElement != null ? servicesElement.Value : string.Empty; var includeServicesString = includeServicesElement != null ? includeServicesElement.Value : string.Empty; var excludeServicesString = excludeServicesElement != null ? excludeServicesElement.Value : string.Empty; if (detector.ProjectAndServiceIsActive( platformsString, includePlatformsString, excludePlatformsString, servicesString, includeServicesString, excludeServicesString, platform, activeServices)) { var include = file.Attribute(XName.Get("Include")); var linkElement = file.XPathSelectElement("Link"); var link = linkElement != null ? linkElement.Value : include.Value; var fileInfo = new FileInfo(Path.Combine( definition.AbsolutePath.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar), include.Value.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar))); if (definition.Type == "Library") { // For libraries, we're copying the content so that applications using // the libraries will have the content. This is most often used to // ship native binaries (although NativeBinary in external projects now // supersedes this). if (link.Contains('/') || link.Contains('\\')) { RedirectableConsole.WriteLine( "WARNING: Copy-on-build file '" + link + "' in library project which " + "does not output to root of project detected. This is not supported."); } else { if (fileInfo.Name != link) { RedirectableConsole.WriteLine( "WARNING: Copy-on-build file in library project does not have the same " + "name when copied to build directory. This is not supported."); } else { var sourcePath = fileInfo.FullName; sourcePath = sourcePath.Substring(rootPath.Length).Replace('\\', '/').TrimStart('/'); var destPath = Path.Combine("_AutomaticExternals", sourcePath); var sourcePathRegex = this.ConvertPathWithMSBuildVariablesFind(sourcePath); var destPathRegex = this.ConvertPathWithMSBuildVariablesReplace(destPath.Replace('\\', '/')); var includeMatch = fileFilter.ApplyInclude(sourcePathRegex); fileFilter.ApplyRewrite(sourcePathRegex, destPathRegex); if (includeMatch) { var nativeBinaryEntry = externalProjectDocument.CreateElement("NativeBinary"); nativeBinaryEntry.SetAttribute("Path", destPath); externalProject.AppendChild(nativeBinaryEntry); } else { throw new InvalidOperationException( "File not found at " + sourcePath + " when converting " + "copy-on-build file in library project."); } } } } } } // Write out the external project to a temporary file and include it. var name = Path.GetRandomFileName() + "_" + definition.Name + ".xml"; var temp = Path.Combine(Path.GetTempPath(), name); temporaryFiles.Add(temp); using (var writer = XmlWriter.Create(temp, new XmlWriterSettings { Indent = true, IndentChars = " " })) { externalProjectDocument.WriteTo(writer); } fileFilter.AddManualMapping(temp, "Build/Projects/" + definition.Name + ".definition"); }
private void AutomaticallyPackageNormalProject( DefinitionInfo[] definitions, List <Service> services, FileFilter fileFilter, string rootPath, string platform, DefinitionInfo definition) { var document = XDocument.Load(definition.DefinitionPath); var platformSpecificOutputFolderElement = document.XPathSelectElement("/Project/Properties/PlatformSpecificOutputFolder"); var projectSpecificOutputFolderElement = document.XPathSelectElement("/Project/Properties/ProjectSpecificOutputFolder"); var assemblyNameForPlatformElement = document.XPathSelectElement("/Project/Properties/AssemblyName/Platform[@Name=\"" + platform + "\"]"); var assemblyNameGlobalElement = document.XPathSelectElement("/Project/Properties/Property[@Name=\"AssemblyName\"]"); var platformSpecificOutputFolder = true; var projectSpecificOutputFolder = false; if (platformSpecificOutputFolderElement != null) { platformSpecificOutputFolder = platformSpecificOutputFolderElement.Value.ToLowerInvariant() != "false"; } if (projectSpecificOutputFolderElement != null) { projectSpecificOutputFolder = projectSpecificOutputFolderElement.Value.ToLowerInvariant() == "true"; } string assemblyName = null; if (assemblyNameForPlatformElement != null) { assemblyName = assemblyNameForPlatformElement.Value; } else if (assemblyNameGlobalElement != null) { assemblyName = assemblyNameGlobalElement.Value; } else { assemblyName = definition.Name; } var assemblyFilesToCopy = new[] { assemblyName + ".exe", assemblyName + ".dll", assemblyName + ".dll.config", assemblyName + ".dll.mdb", assemblyName + ".pdb", assemblyName + ".xml", }; var outputMode = OutputPathMode.BinConfiguration; if (projectSpecificOutputFolder) { outputMode = OutputPathMode.BinProjectPlatformArchConfiguration; } if (platformSpecificOutputFolder) { outputMode = OutputPathMode.BinPlatformArchConfiguration; } var externalProjectDocument = new XmlDocument(); externalProjectDocument.AppendChild(externalProjectDocument.CreateXmlDeclaration("1.0", "UTF-8", null)); var externalProject = externalProjectDocument.CreateElement("ExternalProject"); externalProjectDocument.AppendChild(externalProject); externalProject.SetAttribute("Name", definition.Name); // Copy the assembly itself out to the package. switch (outputMode) { case OutputPathMode.BinConfiguration: { // In this configuration, we only ship the binaries for // the default architecture (because that's all we know // about). We also have to assume the binary folder // contains binaries for the desired platform. var pathPrefix = definition.Path.Replace('\\', '/').Replace(".", "\\.") + "/bin/([^/]+)/"; if (definition.Type == "Library") { // For libraries, we only copy the assembly (and immediately related files) // into it's directory. Native binaries will be expressed through the ExternalProject. foreach (var assemblyFile in assemblyFilesToCopy) { var includeMatch = fileFilter.ApplyInclude("^" + pathPrefix + Regex.Escape(assemblyFile) + "$"); var rewriteMatch = fileFilter.ApplyRewrite("^" + pathPrefix + Regex.Escape(assemblyFile) + "$", definition.Name + "/AnyCPU/" + assemblyFile); if (includeMatch && rewriteMatch) { if (assemblyFile.EndsWith(".dll")) { var binaryEntry = externalProjectDocument.CreateElement("Binary"); binaryEntry.SetAttribute("Name", assemblyFile.Substring(0, assemblyFile.Length - 4)); binaryEntry.SetAttribute("Path", definition.Name + "\\AnyCPU\\" + assemblyFile); externalProject.AppendChild(binaryEntry); } else if (assemblyFile.EndsWith(".dll.config")) { var configEntry = externalProjectDocument.CreateElement("NativeBinary"); configEntry.SetAttribute("Path", definition.Name + "\\AnyCPU\\" + assemblyFile); externalProject.AppendChild(configEntry); } } else if (includeMatch || rewriteMatch) { throw new InvalidOperationException("Automatic filter; only one rule matched."); } } } else { // For executables, we ship everything in the output directory, because we // want the executables to be able to run from the package directory. fileFilter.ApplyInclude("^" + pathPrefix + "(.+)$"); fileFilter.ApplyRewrite("^" + pathPrefix + "(.+)$", definition.Name + "/AnyCPU/$2"); } break; } case OutputPathMode.BinPlatformArchConfiguration: { // In this configuration, we ship binaries for AnyCPU, iPhone or all .NET architectures // depending on whether or not the platform produces multiple architectures. On Mono, // we can't use $(Platform) within a reference's path, so we have to keep this path static // for Mono platforms. string pathArchMatch, pathArchReplace, pathArchRuntime; switch (platform.ToLowerInvariant()) { case "ios": { pathArchMatch = "iPhone"; pathArchReplace = "iPhone"; pathArchRuntime = "iPhone"; break; } case "windowsphone": { pathArchMatch = "([^/]+)"; pathArchReplace = "$1"; pathArchRuntime = "$(Platform)"; break; } default: { pathArchMatch = "AnyCPU"; pathArchReplace = "AnyCPU"; pathArchRuntime = "AnyCPU"; break; } } var pathPrefix = definition.Path.Replace('\\', '/').Replace(".", "\\.") + "/bin/" + platform + "/" + pathArchMatch + "/([^/]+)/"; if (definition.Type == "Library") { // For libraries, we only copy the assembly (and immediately related files) // into it's directory. Native binaries will be expressed through the ExternalProject. foreach (var assemblyFile in assemblyFilesToCopy) { var includeMatch = fileFilter.ApplyInclude("^" + pathPrefix + Regex.Escape(assemblyFile) + "$"); var rewriteMatch = fileFilter.ApplyRewrite("^" + pathPrefix + Regex.Escape(assemblyFile) + "$", definition.Name + "/" + pathArchReplace + "/" + assemblyFile); if (includeMatch && rewriteMatch) { if (assemblyFile.EndsWith(".dll")) { var binaryEntry = externalProjectDocument.CreateElement("Binary"); binaryEntry.SetAttribute("Name", assemblyFile.Substring(0, assemblyFile.Length - 4)); binaryEntry.SetAttribute("Path", definition.Name + "\\" + pathArchRuntime + "\\" + assemblyFile); externalProject.AppendChild(binaryEntry); } else if (assemblyFile.EndsWith(".dll.config")) { var configEntry = externalProjectDocument.CreateElement("NativeBinary"); configEntry.SetAttribute("Path", definition.Name + "\\" + pathArchRuntime + "\\" + assemblyFile); externalProject.AppendChild(configEntry); } } else if (includeMatch || rewriteMatch) { throw new InvalidOperationException("Automatic filter; only one rule matched."); } } } else { // For executables, we ship everything in the output directory, because we // want the executables to be able to run from the package directory. fileFilter.ApplyInclude("^" + pathPrefix + "(.+)$"); if (pathArchMatch == "([^/]+)") { fileFilter.ApplyRewrite("^" + pathPrefix + "(.+)$", definition.Name + "/" + pathArchReplace + "/$3"); } else { fileFilter.ApplyRewrite("^" + pathPrefix + "(.+)$", definition.Name + "/" + pathArchReplace + "/$2"); } } break; } case OutputPathMode.BinProjectPlatformArchConfiguration: { throw new NotSupportedException(); break; } } // Convert all of the known references into references within the external project. var definitionsByName = definitions.ToDictionary(k => k.Name, v => v); foreach (var reference in document.XPathSelectElements("/Project/References/Reference")) { var includeAttribute = reference.Attribute(XName.Get("Include")); if (includeAttribute != null) { if (definitionsByName.ContainsKey(includeAttribute.Value)) { // This reference will be converted to an external project, // so add a reference to it (in case it contains native binaries // which need to be copied out). var referenceEntry = externalProjectDocument.CreateElement("Reference"); referenceEntry.SetAttribute("Include", includeAttribute.Value); externalProject.AppendChild(referenceEntry); } } } // Copy out any files that are marked with copy-on-build flag. var detector = new PlatformAndServiceActiveDetection(); var xmlDocument = new XmlDocument(); xmlDocument.Load(definition.DefinitionPath); var servicesInput = this.m_ServiceInputGenerator.Generate(xmlDocument, definition.Name, services); var activeServicesElement = servicesInput.ChildNodes.OfType <XmlElement>().FirstOrDefault(x => x.LocalName == "ActiveServicesNames"); var activeServices = activeServicesElement.InnerText; foreach (var file in document.XPathSelectElements("/Project/Files/*")) { var copyOnBuild = file.XPathSelectElement("CopyToOutputDirectory"); if (copyOnBuild == null) { continue; } if (copyOnBuild.Value != "PreserveNewest" && copyOnBuild.Value != "Always") { continue; } var platformsElement = file.XPathSelectElement("Platforms"); var includePlatformsElement = file.XPathSelectElement("IncludePlatforms"); var excludePlatformsElement = file.XPathSelectElement("ExcludePlatforms"); var servicesElement = file.XPathSelectElement("Services"); var includeServicesElement = file.XPathSelectElement("IncludeServices"); var excludeServicesElement = file.XPathSelectElement("ExcludeServices"); var platformsString = platformsElement != null ? platformsElement.Value : string.Empty; var includePlatformsString = includePlatformsElement != null ? includePlatformsElement.Value : string.Empty; var excludePlatformsString = excludePlatformsElement != null ? excludePlatformsElement.Value : string.Empty; var servicesString = servicesElement != null ? servicesElement.Value : string.Empty; var includeServicesString = includeServicesElement != null ? includeServicesElement.Value : string.Empty; var excludeServicesString = excludeServicesElement != null ? excludeServicesElement.Value : string.Empty; if (detector.ProjectAndServiceIsActive( platformsString, includePlatformsString, excludePlatformsString, servicesString, includeServicesString, excludeServicesString, platform, activeServices)) { var include = file.Attribute(XName.Get("Include")); var linkElement = file.XPathSelectElement("Link"); var link = linkElement != null ? linkElement.Value : include.Value; var fileInfo = new FileInfo(Path.Combine( definition.ModulePath.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar), definition.Path.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar), include.Value.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar))); if (definition.Type == "Library") { // For libraries, we're copying the content so that applications using // the libraries will have the content. This is most often used to // ship native binaries (although NativeBinary in external projects now // supersedes this). if (link.Contains('/') || link.Contains('\\')) { Console.WriteLine( "WARNING: Copy-on-build file '" + link + "' in library project which " + "does not output to root of project detected. This is not supported."); } else { if (fileInfo.Name != link) { Console.WriteLine( "WARNING: Copy-on-build file in library project does not have the same " + "name when copied to build directory. This is not supported."); } else { var sourcePath = fileInfo.FullName; sourcePath = sourcePath.Substring(rootPath.Length).Replace('\\', '/').TrimStart('/'); var destPath = Path.Combine("_AutomaticExternals", sourcePath); var sourcePathRegex = this.ConvertPathWithMSBuildVariablesFind(sourcePath); var destPathRegex = this.ConvertPathWithMSBuildVariablesReplace(destPath.Replace('\\', '/')); var includeMatch = fileFilter.ApplyInclude(sourcePathRegex); fileFilter.ApplyRewrite(sourcePathRegex, destPathRegex); if (includeMatch) { var nativeBinaryEntry = externalProjectDocument.CreateElement("NativeBinary"); nativeBinaryEntry.SetAttribute("Path", destPath); externalProject.AppendChild(nativeBinaryEntry); } else { throw new InvalidOperationException( "File not found at " + sourcePath + " when converting " + "copy-on-build file in library project."); } } } } } } // Write out the external project to a temporary file and include it. var name = Path.GetRandomFileName() + "_" + definition.Name + ".xml"; var temp = Path.Combine(Path.GetTempPath(), name); using (var writer = XmlWriter.Create(temp, new XmlWriterSettings { Indent = true, IndentChars = " " })) { externalProjectDocument.WriteTo(writer); } fileFilter.AddManualMapping(temp, "Build/Projects/" + definition.Name + ".definition"); }
private void AutomaticallyPackageNormalProject( DefinitionInfo[] definitions, List<Service> services, FileFilter fileFilter, string rootPath, string platform, DefinitionInfo definition, List<string> temporaryFiles) { var document = XDocument.Load(definition.DefinitionPath); var externalProjectDocument = new XmlDocument(); externalProjectDocument.AppendChild(externalProjectDocument.CreateXmlDeclaration("1.0", "UTF-8", null)); var externalProject = externalProjectDocument.CreateElement("ExternalProject"); externalProjectDocument.AppendChild(externalProject); externalProject.SetAttribute("Name", definition.Name); if (definition.PostBuildHook) { externalProject.SetAttribute("PostBuildHook", "True"); } var externalProjectServices = externalProjectDocument.CreateElement("Services"); externalProject.AppendChild(externalProjectServices); // Just import all declared services as available, regardless of conflicts or // requirements. We don't have a clean way of automatically translating // services for packages (it has to be done manually because services can // change the resulting code that's built). So that things work 95% of time, // just declare all services available for projects included via the automatic // packaging mechanism. var servicesToDeclare = new List<string>(); var servicesDeclared = document.Root.Element(XName.Get("Services")); if (servicesDeclared != null) { foreach (var serviceElement in servicesDeclared.Elements().Where(x => x.Name.LocalName == "Service")) { servicesToDeclare.Add(serviceElement.Attribute(XName.Get("Name")).Value); } } foreach (var serviceToDeclare in servicesToDeclare) { var serviceElem = externalProjectDocument.CreateElement("Service"); serviceElem.SetAttribute("Name", serviceToDeclare); var defaultForRoot = externalProjectDocument.CreateElement("DefaultForRoot"); defaultForRoot.InnerText = "True"; serviceElem.AppendChild(defaultForRoot); externalProjectServices.AppendChild(serviceElem); } // Copy all existing references that the normal project makes to the external project. var referencesDeclared = document.Root.Element(XName.Get("References")); if (referencesDeclared != null) { foreach (var referenceElement in referencesDeclared.Elements().Where(x => x.Name.LocalName == "Reference")) { var referenceElem = externalProjectDocument.CreateElement("Reference"); referenceElem.SetAttribute("Include", referenceElement.Attribute(XName.Get("Include")).Value); } } var pathPrefix = this.m_ProjectOutputPathCalculator.GetProjectOutputPathPrefix(platform, definition, document, true); var assemblyName = this.m_ProjectOutputPathCalculator.GetProjectAssemblyName(platform, definition, document); var outputMode = this.m_ProjectOutputPathCalculator.GetProjectOutputMode(document); var assemblyFilesToCopy = new[] { assemblyName + ".exe", assemblyName + ".dll", assemblyName + ".dll.config", assemblyName + ".dll.mdb", assemblyName + ".pdb", assemblyName + ".xml", }; // Copy the assembly itself out to the package. switch (outputMode) { case OutputPathMode.BinConfiguration: { // In this configuration, we only ship the binaries for // the default architecture (because that's all we know // about). We also have to assume the binary folder // contains binaries for the desired platform. if (definition.Type == "Library") { // For libraries, we only copy the assembly (and immediately related files) // into it's directory. Native binaries will be expressed through the ExternalProject. foreach (var assemblyFile in assemblyFilesToCopy) { var includeMatch = fileFilter.ApplyInclude("^" + pathPrefix + Regex.Escape(assemblyFile) + "$"); var rewriteMatch = fileFilter.ApplyRewrite("^" + pathPrefix + Regex.Escape(assemblyFile) + "$", definition.Name + "/AnyCPU/" + assemblyFile); if (includeMatch && rewriteMatch) { if (assemblyFile.EndsWith(".dll")) { var binaryEntry = externalProjectDocument.CreateElement("Binary"); binaryEntry.SetAttribute("Name", assemblyFile.Substring(0, assemblyFile.Length - 4)); binaryEntry.SetAttribute("Path", definition.Name + "\\AnyCPU\\" + assemblyFile); externalProject.AppendChild(binaryEntry); } else if (assemblyFile.EndsWith(".dll.config")) { var configEntry = externalProjectDocument.CreateElement("NativeBinary"); configEntry.SetAttribute("Path", definition.Name + "\\AnyCPU\\" + assemblyFile); externalProject.AppendChild(configEntry); } } else if (includeMatch || rewriteMatch) { throw new InvalidOperationException("Automatic filter; only one rule matched."); } } } else { // For executables, we ship everything in the output directory, because we // want the executables to be able to run from the package directory. fileFilter.ApplyInclude("^" + pathPrefix + "(.+)$"); fileFilter.ApplyRewrite("^" + pathPrefix + "(.+)$", definition.Name + "/AnyCPU/$2"); // Mark the executable files in the directory as tools that can be executed. foreach (var assemblyFile in assemblyFilesToCopy) { if (assemblyFile.EndsWith(".exe")) { var binaryEntry = externalProjectDocument.CreateElement("Tool"); binaryEntry.SetAttribute("Name", assemblyFile.Substring(0, assemblyFile.Length - 4)); binaryEntry.SetAttribute("Path", definition.Name + "\\AnyCPU\\" + assemblyFile); externalProject.AppendChild(binaryEntry); } } } break; } case OutputPathMode.BinPlatformArchConfiguration: { // In this configuration, we ship binaries for AnyCPU, iPhoneSimulator or all .NET architectures // depending on whether or not the platform produces multiple architectures. On Mono, // we can't use $(Platform) within a reference's path, so we have to keep this path static // for Mono platforms. string pathArchMatch, pathArchReplace, pathArchRuntime; switch (platform.ToLowerInvariant()) { case "ios": { pathArchMatch = "iPhoneSimulator"; pathArchReplace = "iPhoneSimulator"; pathArchRuntime = "iPhoneSimulator"; break; } case "windowsphone": { pathArchMatch = "([^/]+)"; pathArchReplace = "$1"; pathArchRuntime = "$(Platform)"; break; } default: { pathArchMatch = "AnyCPU"; pathArchReplace = "AnyCPU"; pathArchRuntime = "AnyCPU"; break; } } if (definition.Type == "Library") { // For libraries, we only copy the assembly (and immediately related files) // into it's directory. Native binaries will be expressed through the ExternalProject. foreach (var assemblyFile in assemblyFilesToCopy) { var includeMatch = fileFilter.ApplyInclude("^" + pathPrefix + Regex.Escape(assemblyFile) + "$"); var rewriteMatch = fileFilter.ApplyRewrite("^" + pathPrefix + Regex.Escape(assemblyFile) + "$", definition.Name + "/" + pathArchReplace + "/" + assemblyFile); if (includeMatch && rewriteMatch) { if (assemblyFile.EndsWith(".dll")) { var binaryEntry = externalProjectDocument.CreateElement("Binary"); binaryEntry.SetAttribute("Name", assemblyFile.Substring(0, assemblyFile.Length - 4)); binaryEntry.SetAttribute("Path", definition.Name + "\\" + pathArchRuntime + "\\" + assemblyFile); externalProject.AppendChild(binaryEntry); } else if (assemblyFile.EndsWith(".dll.config")) { var configEntry = externalProjectDocument.CreateElement("NativeBinary"); configEntry.SetAttribute("Path", definition.Name + "\\" + pathArchRuntime + "\\" + assemblyFile); externalProject.AppendChild(configEntry); } } else if (includeMatch || rewriteMatch) { throw new InvalidOperationException("Automatic filter; only one rule matched."); } } } else { // For executables, we ship everything in the output directory, because we // want the executables to be able to run from the package directory. fileFilter.ApplyInclude("^" + pathPrefix + "(.+)$"); if (pathArchMatch == "([^/]+)") { fileFilter.ApplyRewrite("^" + pathPrefix + "(.+)$", definition.Name + "/" + pathArchReplace + "/$3"); } else { fileFilter.ApplyRewrite("^" + pathPrefix + "(.+)$", definition.Name + "/" + pathArchReplace + "/$2"); } // Mark the executable files in the directory as tools that can be executed. foreach (var assemblyFile in assemblyFilesToCopy) { if (assemblyFile.EndsWith(".exe")) { var binaryEntry = externalProjectDocument.CreateElement("Tool"); binaryEntry.SetAttribute("Name", assemblyFile.Substring(0, assemblyFile.Length - 4)); binaryEntry.SetAttribute("Path", definition.Name + "\\" + pathArchRuntime + "\\" + assemblyFile); externalProject.AppendChild(binaryEntry); } } } break; } case OutputPathMode.BinProjectPlatformArchConfiguration: { throw new NotSupportedException(); break; } } // Convert all of the known references into references within the external project. var definitionsByName = definitions.ToDictionarySafe( k => k.Name, v => v, (dict, x) => { var existing = dict[x.Name]; var tried = x; Console.WriteLine("WARNING: There is more than one project with the name " + x.Name + " (first project loaded from " + tried.AbsolutePath + ", " + "skipped loading second project from " + existing.AbsolutePath + ")"); }); foreach (var reference in document.XPathSelectElements("/Project/References/Reference")) { var includeAttribute = reference.Attribute(XName.Get("Include")); if (includeAttribute != null) { if (definitionsByName.ContainsKey(includeAttribute.Value)) { // This reference will be converted to an external project, // so add a reference to it (in case it contains native binaries // which need to be copied out). var referenceEntry = externalProjectDocument.CreateElement("Reference"); referenceEntry.SetAttribute("Include", includeAttribute.Value); externalProject.AppendChild(referenceEntry); } } } // Copy out any files that are marked with copy-on-build flag. var detector = new PlatformAndServiceActiveDetection(); var xmlDocument = new XmlDocument(); xmlDocument.Load(definition.DefinitionPath); var servicesInput = this.m_ServiceInputGenerator.Generate(xmlDocument, definition.Name, services); var activeServicesElement = servicesInput.ChildNodes.OfType<XmlElement>().FirstOrDefault(x => x.LocalName == "ActiveServicesNames"); var activeServices = activeServicesElement.InnerText; foreach (var file in document.XPathSelectElements("/Project/Files/*")) { var copyOnBuild = file.XPathSelectElement("CopyToOutputDirectory"); if (copyOnBuild == null) { continue; } if (copyOnBuild.Value != "PreserveNewest" && copyOnBuild.Value != "Always") { continue; } var platformsElement = file.XPathSelectElement("Platforms"); var includePlatformsElement = file.XPathSelectElement("IncludePlatforms"); var excludePlatformsElement = file.XPathSelectElement("ExcludePlatforms"); var servicesElement = file.XPathSelectElement("Services"); var includeServicesElement = file.XPathSelectElement("IncludeServices"); var excludeServicesElement = file.XPathSelectElement("ExcludeServices"); var platformsString = platformsElement != null ? platformsElement.Value : string.Empty; var includePlatformsString = includePlatformsElement != null ? includePlatformsElement.Value : string.Empty; var excludePlatformsString = excludePlatformsElement != null ? excludePlatformsElement.Value : string.Empty; var servicesString = servicesElement != null ? servicesElement.Value : string.Empty; var includeServicesString = includeServicesElement != null ? includeServicesElement.Value : string.Empty; var excludeServicesString = excludeServicesElement != null ? excludeServicesElement.Value : string.Empty; if (detector.ProjectAndServiceIsActive( platformsString, includePlatformsString, excludePlatformsString, servicesString, includeServicesString, excludeServicesString, platform, activeServices)) { var include = file.Attribute(XName.Get("Include")); var linkElement = file.XPathSelectElement("Link"); var link = linkElement != null ? linkElement.Value : include.Value; var fileInfo = new FileInfo(Path.Combine( definition.AbsolutePath.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar), include.Value.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar))); if (definition.Type == "Library") { // For libraries, we're copying the content so that applications using // the libraries will have the content. This is most often used to // ship native binaries (although NativeBinary in external projects now // supersedes this). if (link.Contains('/') || link.Contains('\\')) { Console.WriteLine( "WARNING: Copy-on-build file '" + link + "' in library project which " + "does not output to root of project detected. This is not supported."); } else { if (fileInfo.Name != link) { Console.WriteLine( "WARNING: Copy-on-build file in library project does not have the same " + "name when copied to build directory. This is not supported."); } else { var sourcePath = fileInfo.FullName; sourcePath = sourcePath.Substring(rootPath.Length).Replace('\\', '/').TrimStart('/'); var destPath = Path.Combine("_AutomaticExternals", sourcePath); var sourcePathRegex = this.ConvertPathWithMSBuildVariablesFind(sourcePath); var destPathRegex = this.ConvertPathWithMSBuildVariablesReplace(destPath.Replace('\\', '/')); var includeMatch = fileFilter.ApplyInclude(sourcePathRegex); fileFilter.ApplyRewrite(sourcePathRegex, destPathRegex); if (includeMatch) { var nativeBinaryEntry = externalProjectDocument.CreateElement("NativeBinary"); nativeBinaryEntry.SetAttribute("Path", destPath); externalProject.AppendChild(nativeBinaryEntry); } else { throw new InvalidOperationException( "File not found at " + sourcePath + " when converting " + "copy-on-build file in library project."); } } } } } } // Write out the external project to a temporary file and include it. var name = Path.GetRandomFileName() + "_" + definition.Name + ".xml"; var temp = Path.Combine(Path.GetTempPath(), name); temporaryFiles.Add(temp); using (var writer = XmlWriter.Create(temp, new XmlWriterSettings { Indent = true, IndentChars = " " })) { externalProjectDocument.WriteTo(writer); } fileFilter.AddManualMapping(temp, "Build/Projects/" + definition.Name + ".definition"); }