private void ShowSupportedPlatformsError(ModuleInfo module, string requestedPlatform) { RedirectableConsole.ErrorWriteLine("The platform '" + requestedPlatform + "' is not supported."); RedirectableConsole.ErrorWriteLine("The following platforms are supported by this module:"); foreach ( var supportedPlatform in module.SupportedPlatforms.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()) .Where(x => !string.IsNullOrWhiteSpace(x))) { RedirectableConsole.ErrorWriteLine(" * " + supportedPlatform); } ExecEnvironment.Exit(1); }
public int Execute(Execution execution) { if (!File.Exists(Path.Combine("Build", "Module.xml"))) { Directory.CreateDirectory("Build"); ResourceExtractor.ExtractAll(Path.Combine(Environment.CurrentDirectory, "Build"), "MyProject"); Console.WriteLine("Build" + Path.DirectorySeparatorChar + "Module.xml has been created."); ExecEnvironment.Exit(0); } return(this.m_ActionDispatch.DefaultAction( ModuleInfo.Load(Path.Combine("Build", "Module.xml")), enabledServices: execution.EnabledServices.ToArray(), disabledServices: execution.DisabledServices.ToArray(), serviceSpecPath: execution.ServiceSpecificationPath) ? 0 : 1); }
/// <summary> /// Performs a resynchronisation, synchronisation, generation or clean on the specified module. /// </summary> /// <returns><c>true</c>, if the action succeeded, <c>false</c> otherwise.</returns> /// <param name="module">The module to perform the action on.</param> /// <param name="action">The action to perform, either "resync", "sync", "generate" or "clean".</param> /// <param name="platform">The platform to perform the action for.</param> /// <param name="enabledServices">A list of enabled services.</param> /// <param name="disabledServices">A list of disabled services.</param> /// <param name="serviceSpecPath">The service specification path.</param> /// <param name="debugServiceResolution">Whether to enable debugging information during service resolution.</param> /// <param name="disablePackageResolution">Whether to disable package resolution.</param> /// <param name="disableHostPlatformGeneration">Whether to disable generation of the host platform projects.</param> /// <param name="taskParallelisation">Whether to enable or disable task generation, or null for the default behaviour.</param> public bool PerformAction( string workingDirectory, ModuleInfo module, string action, string platform, string[] enabledServices, string[] disabledServices, string serviceSpecPath, bool debugServiceResolution, bool disablePackageResolution, bool disableHostPlatformGeneration, bool?taskParallelisation, bool?safeResolve, bool debugProjectGeneration) { var platformSupplied = !string.IsNullOrWhiteSpace(platform); var hostPlatform = this.m_HostPlatformDetector.DetectPlatform(); if (string.IsNullOrWhiteSpace(platform)) { platform = hostPlatform; } var originalPlatform = platform; string primaryPlatform = null; string multiplePlatforms = null; if (platform.Contains(",")) { // The user requested multiple platforms; the first one // is the primary platform. var platforms = platform.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (platforms.Length == 0) { RedirectableConsole.ErrorWriteLine("You supplied only commas where a list of platforms was expected."); ExecEnvironment.Exit(1); return(false); } else { for (var i = 0; i < platforms.Length; i++) { var newPlatform = _moduleUtilities.NormalizePlatform(module, platforms[i]); if (newPlatform == null) { ShowSupportedPlatformsError(module, platforms[i]); return(false); } platforms[i] = newPlatform; } primaryPlatform = platforms[0]; multiplePlatforms = platforms.Aggregate((a, b) => a + "," + b); } } else { platform = _moduleUtilities.NormalizePlatform(module, platform); if (platform == null && !platformSupplied) { // The current host platform isn't supported, so we shouldn't try to // operate on it. string firstPlatform = null; switch (this.m_HostPlatformDetector.DetectPlatform()) { case "Windows": firstPlatform = module.DefaultWindowsPlatforms.Split(',').FirstOrDefault(); break; case "MacOS": firstPlatform = module.DefaultMacOSPlatforms.Split(',').FirstOrDefault(); break; case "Linux": firstPlatform = module.DefaultLinuxPlatforms.Split(',').FirstOrDefault(); break; } if (firstPlatform != null) { // This will end up null if the first platform isn't supported // either and hence throw the right message. platform = _moduleUtilities.NormalizePlatform(module, firstPlatform); } } if (platform == null) { ShowSupportedPlatformsError(module, originalPlatform); return(false); } primaryPlatform = platform; } // You can generate multiple targets by default by setting the <DefaultWindowsPlatforms> // <DefaultMacOSPlatforms> and <DefaultLinuxPlatforms> tags in Module.xml. Note that // synchronisation will only be done for the primary platform, as there is no correct // synchronisation behaviour when dealing with multiple C# projects. // // We only trigger this behaviour when the platform is omitted; if you explicitly // specify "Windows" on the command line, we'll only generate / resync / sync // the Windows platform. if (!platformSupplied) { switch (platform) { case "Windows": multiplePlatforms = module.DefaultWindowsPlatforms; break; case "MacOS": multiplePlatforms = module.DefaultMacOSPlatforms; break; case "Linux": multiplePlatforms = module.DefaultLinuxPlatforms; break; } } // If no overrides are set, just use the current platform. if (string.IsNullOrEmpty(multiplePlatforms)) { multiplePlatforms = platform; } // If running pure synchronisation or a project clean, we don't need to perform // package resolution. if (action.ToLower() == "sync" || action.ToLower() == "clean") { disablePackageResolution = true; } // Resolve submodules as needed. if (!disablePackageResolution) { this.m_PackageManager.ResolveAll(workingDirectory, module, primaryPlatform, taskParallelisation, false, safeResolve); } // Create the list of multiple platforms. var multiplePlatformsList = multiplePlatforms.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToList(); // Remember whether or not we need to implicitly generate the host // platform. var implicitlyGenerateHostPlatform = false; Action requiresHostPlatform = () => implicitlyGenerateHostPlatform = true; // If we are already generating the host platform, then requiring the // host platform is already satisifed. if (platform == hostPlatform || multiplePlatformsList.Contains(hostPlatform)) { requiresHostPlatform = () => {}; } else if (!_featureManager.IsFeatureEnabled(Feature.HostPlatformGeneration)) { requiresHostPlatform = () => { RedirectableConsole.ErrorWriteLine( "WARNING: One or more projects requires host platforms to be generated, " + "but the HostPlatformGeneration feature is not enabled. Expect your " + "build to fail."); }; } // You can configure the default action for Protobuild in their project // with the <DefaultAction> tag in Module.xml. If omitted, default to a resync. // Valid options for this tag are either "Generate", "Resync" or "Sync". // If the actions are "Resync" or "Sync", then we need to perform an initial // step against the primary platform. switch (action.ToLower()) { case "generate": if (!this.GenerateProjectsForPlatform( workingDirectory, module, primaryPlatform, enabledServices, disabledServices, serviceSpecPath, debugServiceResolution, disablePackageResolution, disableHostPlatformGeneration, requiresHostPlatform, debugProjectGeneration)) { return(false); } break; case "resync": if (!this.ResyncProjectsForPlatform( workingDirectory, module, primaryPlatform, enabledServices, disabledServices, serviceSpecPath, debugServiceResolution, disablePackageResolution, disableHostPlatformGeneration, requiresHostPlatform, debugProjectGeneration)) { return(false); } break; case "sync": return(this.SyncProjectsForPlatform(module, primaryPlatform)); case "clean": if (!this.CleanProjectsForPlatform(module, primaryPlatform)) { return(false); } break; default: RedirectableConsole.ErrorWriteLine("Unknown option in <DefaultAction> tag of Module.xml. Defaulting to resync!"); return(this.ResyncProjectsForPlatform( workingDirectory, module, primaryPlatform, enabledServices, disabledServices, serviceSpecPath, debugServiceResolution, disablePackageResolution, disableHostPlatformGeneration, requiresHostPlatform, debugProjectGeneration)); } // Now iterate through the multiple platforms specified. foreach (var platformIter in multiplePlatformsList.Distinct()) { if (platformIter == primaryPlatform) { // Already handled above. continue; } // Resolve submodules as needed. if (!disablePackageResolution) { this.m_PackageManager.ResolveAll(workingDirectory, module, platformIter, taskParallelisation, false, safeResolve); } switch (action.ToLower()) { case "generate": case "resync": // We do a generate under resync mode since we only want the primary platform // to have synchronisation done (and it has had above). if (!this.GenerateProjectsForPlatform( workingDirectory, module, platformIter, enabledServices, disabledServices, serviceSpecPath, debugServiceResolution, disablePackageResolution, disableHostPlatformGeneration, requiresHostPlatform, debugProjectGeneration)) { return(false); } break; case "clean": if (!this.CleanProjectsForPlatform(module, platformIter)) { return(false); } break; default: throw new InvalidOperationException("Code should never reach this point"); } } // If we implicitly require the host platform, generate that now (this variable can // only ever be set to true if the host platform is not already in the list of // platforms generated previously). if (implicitlyGenerateHostPlatform) { // Check to see if the host platform is supported. var hostPlatformNormalized = _moduleUtilities.NormalizePlatform(module, hostPlatform); if (hostPlatformNormalized == null) { RedirectableConsole.WriteLine( "WARNING: The current host platform is not a supported platform for the solution. IDE editor " + "projects and post-build hooks will not be available, and this may cause the project to be " + "built incorrectly!"); return(true); } RedirectableConsole.WriteLine( "One or more projects required the presence of host platform " + "projects, implicitly starting generation for " + hostPlatform + "..."); // Resolve submodules as needed. if (!disablePackageResolution) { this.m_PackageManager.ResolveAll(workingDirectory, module, hostPlatform, taskParallelisation, false, safeResolve); } switch (action.ToLower()) { case "generate": case "resync": // We do a generate under resync mode since we only want the primary platform // to have synchronisation done (and it has had above). if (!this.GenerateProjectsForPlatform( workingDirectory, module, hostPlatform, enabledServices, disabledServices, serviceSpecPath, debugServiceResolution, disablePackageResolution, disableHostPlatformGeneration, requiresHostPlatform, debugProjectGeneration)) { return(false); } break; case "clean": if (!this.CleanProjectsForPlatform(module, hostPlatform)) { return(false); } break; default: throw new InvalidOperationException("Code should never reach this point"); } } // All the steps succeeded, so return true. return(true); }
public byte[] Transform(string workingDirectory, string url, string gitReference, string platform, string format) { var urlAndPackageName = url.Split(new[] { '|' }, 2); if (urlAndPackageName.Length != 2) { RedirectableConsole.ErrorWriteLine( "ERROR: Malformed NuGet package reference '" + url + "'. Make sure you split the NuGet server URL and the package name with a pipe character (|)."); ExecEnvironment.Exit(1); } var repoUrl = urlAndPackageName[0]; var packageName = urlAndPackageName[1]; var originalFolder = DownloadOrUseExistingNuGetPackage(repoUrl.TrimEnd('/'), packageName, gitReference); if (Directory.Exists(Path.Combine(originalFolder, "protobuild"))) { // This is a Protobuild-aware NuGet package. In this case, we just use the contents of the // "protobuild" folder as the content of our package, and ignore everything else. RedirectableConsole.WriteLine("Detected Protobuild-aware package..."); RedirectableConsole.WriteLine("Converting to a Protobuild package..."); var target = new MemoryStream(); var filter = new FileFilter(_getRecursiveUtilitiesInPath.GetRecursiveFilesInPath(originalFolder)); filter.ApplyInclude("protobuild/(.*)"); filter.ApplyRewrite("protobuild/(.*)", "$1"); filter.ImplyDirectories(); _packageCreator.Create( target, filter, originalFolder, format, platform); RedirectableConsole.WriteLine("Package conversion complete."); var bytes2 = new byte[target.Position]; target.Seek(0, SeekOrigin.Begin); target.Read(bytes2, 0, bytes2.Length); return(bytes2); } var folder = Path.GetTempFileName(); File.Delete(folder); Directory.CreateDirectory(folder); byte[] bytes; try { RedirectableConsole.WriteLine("Copying directory for package transformation..."); CopyFolder(new DirectoryInfo(originalFolder), new DirectoryInfo(folder)); RedirectableConsole.WriteLine("Auto-detecting libraries to reference from NuGet package..."); var packagePath = new DirectoryInfo(folder).GetFiles("*.nuspec").First().FullName; var libraryReferences = new Dictionary <string, string>(); var packageDependencies = new Dictionary <string, string>(); // Use the nuspec file if it exists. List <string> references = new List <string>(); if (File.Exists(packagePath)) { var packageDoc = new XmlDocument(); packageDoc.Load(packagePath); // If the references are explicitly provided in the nuspec, use // those as to what files should be referenced by the projects. if ( packageDoc.DocumentElement.FirstChild.ChildNodes.OfType <XmlElement>() .Count(x => x.Name == "references") > 0) { references = packageDoc.DocumentElement.FirstChild.ChildNodes.OfType <XmlElement>() .First(x => x.Name == "references") .ChildNodes.OfType <XmlElement>() .Where(x => x.Name == "reference") .Select(x => x.Attributes["file"].Value) .ToList(); } // If there are dependencies specified, store them and convert them to // Protobuild references, and reference them in the Module.xml file. if ( packageDoc.DocumentElement.FirstChild.ChildNodes.OfType <XmlElement>() .Count(x => x.Name == "dependencies") > 0) { packageDependencies = packageDoc.DocumentElement.FirstChild.ChildNodes.OfType <XmlElement>() .First(x => x.Name == "dependencies") .ChildNodes.OfType <XmlElement>() .Where(x => x.Name == "dependency") .ToDictionarySafe( k => k.Attributes["id"].Value, v => v.Attributes["version"].Value, (dict, c) => RedirectableConsole.WriteLine("WARNING: More than one dependency on " + c + " in NuGet package.")); } } // Determine the priority of the frameworks that we want to target // out of the available versions. string[] clrNames = _nuGetPlatformMapping.GetFrameworkNamesForRead(workingDirectory, platform); var referenceDirectories = new string[] { "ref", "lib" }; foreach (var directory in referenceDirectories) { // Determine the base path for all references; that is, the lib/ folder. var referenceBasePath = Path.Combine( folder, directory); if (Directory.Exists(referenceBasePath)) { // If no references are in nuspec, reference all of the libraries that // are on disk. if (references.Count == 0) { // Search through all of the target frameworks until we find one that // has at least one file in it. foreach (var clrNameOriginal in clrNames) { var clrName = clrNameOriginal; var foundClr = false; if (clrName[0] == '=') { // Exact match (strip the equals). clrName = clrName.Substring(1); // If this target framework doesn't exist for this library, skip it. var dirPath = Path.Combine( referenceBasePath, clrName); if (!Directory.Exists(dirPath)) { continue; } } else if (clrName[0] == '?') { // Substring, search the reference base path for any folders // with a matching substring. clrName = clrName.Substring(1); var baseDirPath = referenceBasePath; var found = false; foreach (var subdir in new DirectoryInfo(baseDirPath).GetDirectories()) { if (subdir.Name.Contains(clrName)) { clrName = subdir.Name; found = true; break; } } if (!found) { continue; } } else { throw new InvalidOperationException("Unknown CLR name match type with '" + clrName + "'"); } // Otherwise enumerate through all of the libraries in this folder. foreach (var dll in Directory.EnumerateFiles( Path.Combine( referenceBasePath, clrName), "*.dll")) { // Determine the relative path to the library. var packageDll = Path.Combine( referenceBasePath, clrName, Path.GetFileName(dll)); // Confirm again that the file actually exists on disk when // combined with the root path. if (File.Exists( Path.Combine( packageDll))) { // Create the library reference. if (!libraryReferences.ContainsKey(Path.GetFileNameWithoutExtension(dll))) { libraryReferences.Add( Path.GetFileNameWithoutExtension(dll), packageDll); } // Mark this target framework as having provided at least // one reference. foundClr = true; } } // Break if we have found at least one reference. if (foundClr) { break; } } } // For all of the references that were found in the original nuspec file, // add those references. foreach (var reference in references) { // Search through all of the target frameworks until we find the one // that has the reference in it. foreach (var clrName in clrNames) { // If this target framework doesn't exist for this library, skip it. var packageDll = Path.Combine( referenceBasePath, clrName, reference); if (File.Exists( Path.Combine( packageDll))) { if (!libraryReferences.ContainsKey(Path.GetFileNameWithoutExtension(packageDll))) { libraryReferences.Add( Path.GetFileNameWithoutExtension(packageDll), packageDll); } break; } } } } } foreach (var kv in libraryReferences) { RedirectableConsole.WriteLine("Found library to reference: " + kv.Key + " (at " + kv.Value + ")"); } RedirectableConsole.WriteLine("Generating external project reference..."); var document = new XmlDocument(); var externalProject = document.CreateElement("ExternalProject"); externalProject.SetAttribute("Name", packageName); document.AppendChild(externalProject); foreach (var kv in libraryReferences) { var binaryReference = document.CreateElement("Binary"); binaryReference.SetAttribute("Name", kv.Key); binaryReference.SetAttribute("Path", kv.Value.Substring(folder.Length).TrimStart(new[] { '/', '\\' }).Replace("%2B", "-")); externalProject.AppendChild(binaryReference); } foreach (var package in packageDependencies) { var externalReference = document.CreateElement("Reference"); externalReference.SetAttribute("Include", package.Key); externalProject.AppendChild(externalReference); } document.Save(Path.Combine(folder, "_ProtobuildExternalProject.xml")); RedirectableConsole.WriteLine("Generating module..."); var generatedModule = new ModuleInfo(); generatedModule.Name = packageName; generatedModule.Packages = new List <PackageRef>(); foreach (var package in packageDependencies) { generatedModule.Packages.Add(new PackageRef { Uri = repoUrl.Replace("http://", "http-nuget://").Replace("https://", "https-nuget://") + "|" + package.Key, GitRef = package.Value.TrimStart('[').TrimEnd(']'), Folder = package.Key }); } generatedModule.Save(Path.Combine(folder, "_ProtobuildModule.xml")); RedirectableConsole.WriteLine("Converting to a Protobuild package..."); var target = new MemoryStream(); var filter = new FileFilter(_getRecursiveUtilitiesInPath.GetRecursiveFilesInPath(folder)); foreach (var kv in libraryReferences) { filter.ApplyInclude( Regex.Escape(kv.Value.Substring(folder.Length).Replace('\\', '/').TrimStart('/'))); filter.ApplyRewrite( Regex.Escape(kv.Value.Substring(folder.Length).Replace('\\', '/').TrimStart('/')), kv.Value.Substring(folder.Length).Replace('\\', '/').TrimStart('/').Replace("%2B", "-")); } filter.ApplyInclude("_ProtobuildExternalProject\\.xml"); filter.ApplyRewrite("_ProtobuildExternalProject\\.xml", "Build/Projects/" + packageName + ".definition"); filter.ApplyInclude("_ProtobuildModule\\.xml"); filter.ApplyRewrite("_ProtobuildModule\\.xml", "Build/Module.xml"); filter.ImplyDirectories(); _packageCreator.Create( target, filter, folder, format, platform); RedirectableConsole.WriteLine("Package conversion complete."); bytes = new byte[target.Position]; target.Seek(0, SeekOrigin.Begin); target.Read(bytes, 0, bytes.Length); } finally { RedirectableConsole.WriteLine("Cleaning up temporary data..."); PathUtils.AggressiveDirectoryDelete(folder); } return(bytes); }
public static void Main(string[] args) { // Ensure we always use the invariant culture in Protobuild. Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; // Set our SSL trust policy. Because Mono doesn't ship with root certificates // on most Linux distributions, we have to be a little more insecure here than // I'd like. For protobuild.org we always verify that the root of the certificate // chain matches what we expect (so people can't forge a certificate from a // *different CA*), but for other domains we just have to implicitly trust them // on Linux since we have no root store. if (Path.DirectorySeparatorChar == '/' && !Directory.Exists("/Library")) { ServicePointManager.ServerCertificateValidationCallback = SSLValidationForLinux; } var kernel = new LightweightKernel(); kernel.BindCore(); kernel.BindBuildResources(); kernel.BindGeneration(); kernel.BindJSIL(); kernel.BindTargets(); kernel.BindFileFilter(); kernel.BindPackages(); kernel.BindAutomatedBuild(); var featureManager = kernel.Get <IFeatureManager>(); featureManager.LoadFeaturesForCurrentDirectory(); var commandMappings = new Dictionary <string, ICommand> { { "sync", kernel.Get <SyncCommand>() }, { "resync", kernel.Get <ResyncCommand>() }, { "generate", kernel.Get <GenerateCommand>() }, { "build", kernel.Get <BuildCommand>() }, { "build-target", kernel.Get <BuildTargetCommand>() }, { "build-property", kernel.Get <BuildPropertyCommand>() }, { "build-process-arch", kernel.Get <BuildProcessArchCommand>() }, { "clean", kernel.Get <CleanCommand>() }, { "automated-build", kernel.Get <AutomatedBuildCommand>() }, { "extract-xslt", kernel.Get <ExtractXSLTCommand>() }, { "enable", kernel.Get <EnableServiceCommand>() }, { "disable", kernel.Get <DisableServiceCommand>() }, { "debug-service-resolution", kernel.Get <DebugServiceResolutionCommand>() }, { "simulate-host-platform", kernel.Get <SimulateHostPlatformCommand>() }, { "spec", kernel.Get <ServiceSpecificationCommand>() }, { "query-features", kernel.Get <QueryFeaturesCommand>() }, { "features", kernel.Get <FeaturesCommand>() }, { "add", kernel.Get <AddPackageCommand>() }, { "list", kernel.Get <ListPackagesCommand>() }, { "install", kernel.Get <InstallPackageCommand>() }, { "upgrade", kernel.Get <UpgradePackageCommand>() }, { "upgrade-all", kernel.Get <UpgradeAllPackagesCommand>() }, { "pack", kernel.Get <PackPackageCommand>() }, { "format", kernel.Get <FormatPackageCommand>() }, { "push", kernel.Get <PushPackageCommand>() }, { "ignore-on-existing", kernel.Get <IgnoreOnExistingPackageCommand>() }, { "repush", kernel.Get <RepushPackageCommand>() }, { "resolve", kernel.Get <ResolveCommand>() }, { "no-resolve", kernel.Get <NoResolveCommand>() }, { "redirect", kernel.Get <RedirectPackageCommand>() }, { "swap-to-source", kernel.Get <SwapToSourceCommand>() }, { "swap-to-binary", kernel.Get <SwapToBinaryCommand>() }, { "start", kernel.Get <StartCommand>() }, { "no-generate", kernel.Get <NoGenerateCommand>() }, { "no-host-generate", kernel.Get <NoHostGenerateCommand>() }, { "execute", kernel.Get <ExecuteCommand>() }, { "execute-configuration", kernel.Get <ExecuteConfigurationCommand>() }, }; var execution = new Execution(); execution.CommandToExecute = kernel.Get <DefaultCommand>(); var options = new Options(); foreach (var kv in commandMappings) { var key = kv.Key; var value = kv.Value; Action <string[]> handle = x => { if (value.IsRecognised()) { value.Encounter(execution, x); } else if (value.IsIgnored()) { } else { throw new InvalidOperationException("Unknown argument '" + key + "'"); } }; if (value.GetArgCount() == 0) { options[key] = handle; } else { options[key + "@" + value.GetArgCount()] = handle; } } Action <string[]> helpAction = x => { PrintHelp(commandMappings); ExecEnvironment.Exit(0); }; options["help"] = helpAction; options["?"] = helpAction; if (ExecEnvironment.DoNotWrapExecutionInTry) { options.Parse(args); } else { try { options.Parse(args); } catch (InvalidOperationException ex) { Console.WriteLine(ex.Message); PrintHelp(commandMappings); ExecEnvironment.Exit(1); } } featureManager.ValidateEnabledFeatures(); if (ExecEnvironment.DoNotWrapExecutionInTry) { var exitCode = execution.CommandToExecute.Execute(execution); ExecEnvironment.Exit(exitCode); } else { try { var exitCode = execution.CommandToExecute.Execute(execution); ExecEnvironment.Exit(exitCode); } catch (ExecEnvironment.SelfInvokeExitException) { throw; } catch (Exception ex) { Console.WriteLine(ex); ExecEnvironment.Exit(1); } } }
public Tuple <int, string, string> RunProtobuild(ModuleInfo module, string args, bool capture = false) { var invokeInline = false; var myHash = ExecEnvironment.GetProgramHash(); var targetHash = ExecEnvironment.GetProgramHash(Path.Combine(module.Path, "Protobuild.exe")); if (myHash == targetHash) { invokeInline = true; } if ((ExecEnvironment.RunProtobuildInProcess || invokeInline) && !capture) { var old = Environment.CurrentDirectory; try { Environment.CurrentDirectory = module.Path; return(new Tuple <int, string, string>( ExecEnvironment.InvokeSelf(args.SplitCommandLine().ToArray()), string.Empty, string.Empty)); } finally { Environment.CurrentDirectory = old; } } var protobuildPath = Path.Combine(module.Path, "Protobuild.exe"); try { var chmodStartInfo = new ProcessStartInfo { FileName = "chmod", Arguments = "a+x Protobuild.exe", WorkingDirectory = module.Path, CreateNoWindow = capture, UseShellExecute = false }; Process.Start(chmodStartInfo); } catch (ExecEnvironment.SelfInvokeExitException) { throw; } catch { } var stdout = string.Empty; var stderr = string.Empty; for (var attempt = 0; attempt < 3; attempt++) { if (File.Exists(protobuildPath)) { var pi = new ProcessStartInfo { FileName = protobuildPath, Arguments = args, WorkingDirectory = module.Path, CreateNoWindow = capture, RedirectStandardError = capture, RedirectStandardInput = capture, RedirectStandardOutput = capture, UseShellExecute = false }; var p = new Process { StartInfo = pi }; if (capture) { p.OutputDataReceived += (sender, eventArgs) => { if (!string.IsNullOrEmpty(eventArgs.Data)) { if (capture) { stdout += eventArgs.Data + "\n"; } else { Console.WriteLine(eventArgs.Data); } } }; p.ErrorDataReceived += (sender, eventArgs) => { if (!string.IsNullOrEmpty(eventArgs.Data)) { if (capture) { stderr += eventArgs.Data + "\n"; } else { Console.Error.WriteLine(eventArgs.Data); } } }; } try { p.Start(); } catch (System.ComponentModel.Win32Exception ex) { if (ex.Message.Contains("Cannot find the specified file")) { // Mono sometimes throws this error even though the // file does exist on disk. The best guess is there's // a race condition between performing chmod on the // file and Mono actually seeing it as an executable file. // Show a warning and sleep for a bit before retrying. if (attempt != 2) { Console.WriteLine("WARNING: Unable to execute Protobuild.exe, will retry again..."); System.Threading.Thread.Sleep(2000); continue; } else { Console.WriteLine("ERROR: Still unable to execute Protobuild.exe."); throw; } } } if (capture) { p.BeginOutputReadLine(); p.BeginErrorReadLine(); } p.WaitForExit(); return(new Tuple <int, string, string>(p.ExitCode, stdout, stderr)); } } return(new Tuple <int, string, string>(1, string.Empty, string.Empty)); }
/// <summary> /// Generates a project at the target path. /// </summary> /// <param name="current">The current project to generate.</param> /// <param name="definitions">A list of all loaded project definitions.</param> /// <param name="workingDirectory"></param> /// <param name="rootPath">The module root path, with directory seperator appended.</param> /// <param name="projectName">The project name.</param> /// <param name="platformName">The platform name.</param> /// <param name="services">A list of services.</param> /// <param name="packagesFilePath"> /// Either the full path to the packages.config for the /// generated project if it exists, or an empty string. /// </param> /// <param name="onActualGeneration"></param> /// <param name="debugProjectGeneration">Whether to emit a .input file for XSLT debugging.</param> public void Generate( DefinitionInfo current, List <LoadedDefinitionInfo> definitions, string workingDirectory, string rootPath, string projectName, string platformName, List <Service> services, out string packagesFilePath, Action onActualGeneration, bool debugProjectGeneration) { packagesFilePath = string.Empty; // Work out what document this is. var projectDoc = definitions.First( x => x.Project.DocumentElement.Attributes["Name"].Value == projectName)?.Project; // Check to see if we have a Project node; don't process it. if (projectDoc?.DocumentElement == null) { return; } // If this is a custom project, run any custom generation commands for // the current host and target platform. if (projectDoc.DocumentElement.Name == "CustomProject") { onActualGeneration(); var onGenerateElements = projectDoc.DocumentElement.SelectNodes("OnGenerate"); if (onGenerateElements != null) { foreach (var onGenerateElement in onGenerateElements.OfType <XmlElement>()) { var matches = true; var hostName = onGenerateElement.GetAttribute("HostName"); var targetName = onGenerateElement.GetAttribute("TargetName"); if (!string.IsNullOrWhiteSpace(hostName)) { if (hostName != _hostPlatformDetector.DetectPlatform()) { matches = false; } } if (!string.IsNullOrWhiteSpace(targetName)) { if (targetName != platformName) { matches = false; } } if (matches) { var command = onGenerateElement.SelectSingleNode("Command")?.InnerText.Trim(); var arguments = onGenerateElement.SelectSingleNode("Arguments")?.InnerText.Trim() ?? string.Empty; if (!string.IsNullOrWhiteSpace(command)) { var workingPath = Path.Combine( rootPath, projectDoc.DocumentElement.Attributes["Path"].Value); var resolvedCommandPath = Path.Combine( workingPath, command); if (File.Exists(resolvedCommandPath)) { var process = Process.Start(new ProcessStartInfo(resolvedCommandPath, arguments) { WorkingDirectory = workingPath, UseShellExecute = false }); if (process == null) { RedirectableConsole.ErrorWriteLine("ERROR: Process did not start when running " + resolvedCommandPath + " " + arguments); ExecEnvironment.Exit(1); return; } process.WaitForExit(); if (process.ExitCode != 0) { RedirectableConsole.ErrorWriteLine( "ERROR: Non-zero exit code " + process.ExitCode); ExecEnvironment.Exit(1); return; } } } } } } } // If this is not a normal project at this point, don't process it. if (projectDoc.DocumentElement.Name != "Project") { return; } // Load the appropriate project transformation XSLT. var languageAttribute = projectDoc.DocumentElement.Attributes["Language"]; var languageText = languageAttribute != null ? languageAttribute.Value : "C#"; var language = this.m_LanguageStringProvider.GetLanguageFromConfigurationName(languageText); var projectTransform = this.m_ResourceProvider.LoadXSLT(workingDirectory, ResourceType.GenerateProject, language, platformName); // Work out what platforms this project should be generated for. var platformAttribute = projectDoc.DocumentElement.Attributes["Platforms"]; string[] allowedPlatforms = null; if (platformAttribute != null) { allowedPlatforms = platformAttribute.Value .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()) .ToArray(); } // Filter on allowed platforms. if (allowedPlatforms != null) { var allowed = false; foreach (var platform in allowedPlatforms) { if (string.Compare(platformName, platform, StringComparison.InvariantCultureIgnoreCase) == 0) { allowed = true; break; } } if (!allowed) { return; } } // If the project has a <Services> node, but there are no entries in /Input/Services, // then nothing depends on this service (if there is a <Reference> to this project, it will // use the default service). So for service-aware projects without any services being // referenced, we exclude them from the generation. if (this.m_ExcludedServiceAwareProjectDetector.IsExcludedServiceAwareProject( projectDoc.DocumentElement.Attributes["Name"].Value, projectDoc, services)) { return; } // Inform the user we're generating this project. onActualGeneration(); // Add include projects if they have an AppliesTo tag that matches this project's name. this._includeProjectAppliesToUpdater.UpdateProjectReferences(definitions.Select(x => x.Project).ToList(), projectDoc); // Add references and properties from include projects. _includeProjectMerger.MergeInReferencesAndPropertiesForIncludeProjects(definitions, projectDoc, platformName); // Imply external project references from other external projects. We do // this so that external projects can reference other external projects (which // we can't reasonably handle at the XSLT level since it's recursive). this.m_ExternalProjectReferenceResolver.ResolveExternalProjectReferences(definitions, projectDoc, platformName); // Generate Info.plist files if necessary (for Mac / iOS). this._mPlatformResourcesGenerator.GenerateInfoPListIfNeeded(definitions, current, projectDoc, platformName); // Work out what path to save at. var path = Path.Combine( rootPath, projectDoc.DocumentElement.Attributes["Path"].Value .Replace('\\', Path.DirectorySeparatorChar) .Replace('/', Path.DirectorySeparatorChar), projectDoc.DocumentElement.Attributes["Name"].Value + "." + platformName + "." + this.m_LanguageStringProvider.GetProjectExtension(language)); // Make sure that the directory exists where the file will be stored. var targetFile = new FileInfo(path); if (!targetFile.Directory.Exists) { targetFile.Directory.Create(); } path = targetFile.FullName; // Handle NuGet packages.config early so that it'll be in place // when the generator automatically determined dependencies. this.m_NuGetConfigMover.Move(rootPath, platformName, projectDoc); // Work out what path the NuGet packages.config might be at. var packagesFile = new FileInfo( Path.Combine( rootPath, projectDoc.DocumentElement.Attributes["Path"].Value .Replace('\\', Path.DirectorySeparatorChar) .Replace('/', Path.DirectorySeparatorChar), "packages.config")); // Generate the input document. var input = this.m_ProjectInputGenerator.Generate( definitions.Select(x => x.Project).ToList(), workingDirectory, rootPath, projectName, platformName, packagesFile.FullName, projectDoc.DocumentElement.ChildNodes .OfType <XmlElement>() .Where(x => x.Name.ToLower() == "properties") .SelectMany(x => x.ChildNodes .OfType <XmlElement>()), services); var settings = new XmlWriterSettings(); settings.Indent = true; if (debugProjectGeneration) { using (var writer = XmlWriter.Create(path + ".input", settings)) { input.Save(writer); } } // Transform the input document using the XSLT transform. using (var writer = XmlWriter.Create(path, settings)) { projectTransform.Transform(input, writer); } // Also remove any left over .sln or .userprefs files. var slnPath = Path.Combine( rootPath, projectDoc.DocumentElement.Attributes["Path"].Value, projectDoc.DocumentElement.Attributes["Name"].Value + "." + platformName + ".sln"); var userprefsPath = Path.Combine( rootPath, projectDoc.DocumentElement.Attributes["Path"].Value, projectDoc.DocumentElement.Attributes["Name"].Value + "." + platformName + ".userprefs"); if (File.Exists(slnPath)) { File.Delete(slnPath); } if (File.Exists(userprefsPath)) { File.Delete(userprefsPath); } // Only return the package file path if it exists. if (packagesFile.Exists) { packagesFilePath = packagesFile.FullName; } }
public static void Main(string[] args) { // Ensure we always use the invariant culture in Protobuild. Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; var kernel = new LightweightKernel(); kernel.BindCore(); kernel.BindBuildResources(); kernel.BindGeneration(); kernel.BindJSIL(); kernel.BindTargets(); kernel.BindFileFilter(); kernel.BindPackages(); var commandMappings = new Dictionary <string, ICommand> { { "sync", kernel.Get <SyncCommand>() }, { "resync", kernel.Get <ResyncCommand>() }, { "generate", kernel.Get <GenerateCommand>() }, { "clean", kernel.Get <CleanCommand>() }, { "extract-xslt", kernel.Get <ExtractXSLTCommand>() }, { "enable", kernel.Get <EnableServiceCommand>() }, { "disable", kernel.Get <DisableServiceCommand>() }, { "spec", kernel.Get <ServiceSpecificationCommand>() }, { "add", kernel.Get <AddPackageCommand>() }, { "upgrade", kernel.Get <UpgradePackageCommand>() }, { "upgrade-all", kernel.Get <UpgradeAllPackagesCommand>() }, { "pack", kernel.Get <PackPackageCommand>() }, { "format", kernel.Get <FormatPackageCommand>() }, { "push", kernel.Get <PushPackageCommand>() }, { "resolve", kernel.Get <ResolveCommand>() }, { "redirect", kernel.Get <RedirectPackageCommand>() }, { "swap-to-source", kernel.Get <SwapToSourceCommand>() }, { "swap-to-binary", kernel.Get <SwapToBinaryCommand>() }, { "start", kernel.Get <StartCommand>() }, }; var execution = new Execution(); execution.CommandToExecute = kernel.Get <DefaultCommand>(); var options = new Options(); foreach (var kv in commandMappings) { var key = kv.Key; var value = kv.Value; if (value.GetArgCount() == 0) { options[key] = x => { value.Encounter(execution, x); }; } else { options[key + "@" + value.GetArgCount()] = x => { value.Encounter(execution, x); }; } } Action <string[]> helpAction = x => { PrintHelp(commandMappings); ExecEnvironment.Exit(0); }; options["help"] = helpAction; options["?"] = helpAction; try { options.Parse(args); } catch (InvalidOperationException ex) { Console.WriteLine(ex.Message); PrintHelp(commandMappings); ExecEnvironment.Exit(1); } try { var exitCode = execution.CommandToExecute.Execute(execution); ExecEnvironment.Exit(exitCode); } catch (ExecEnvironment.SelfInvokeExitException) { throw; } catch (Exception ex) { Console.WriteLine(ex); ExecEnvironment.Exit(1); } }
/// <summary> /// Performs a resynchronisation, synchronisation, generation or clean on the specified module. /// </summary> /// <returns><c>true</c>, if the action succeeded, <c>false</c> otherwise.</returns> /// <param name="module">The module to perform the action on.</param> /// <param name="action">The action to perform, either "resync", "sync", "generate" or "clean".</param> /// <param name="platform">The platform to perform the action for.</param> /// <param name="enabledServices">A list of enabled services.</param> /// <param name="disabledServices">A list of disabled services.</param> /// <param name="serviceSpecPath">The service specification path.</param> public bool PerformAction( ModuleInfo module, string action, string platform = null, string[] enabledServices = null, string[] disabledServices = null, string serviceSpecPath = null) { var platformSupplied = !string.IsNullOrWhiteSpace(platform); if (string.IsNullOrWhiteSpace(platform)) { platform = this.m_HostPlatformDetector.DetectPlatform(); } var originalPlatform = platform; platform = module.NormalizePlatform(platform); if (platform == null && !platformSupplied) { // The current host platform isn't supported, so we shouldn't try to // operate on it. string firstPlatform = null; switch (this.m_HostPlatformDetector.DetectPlatform()) { case "Windows": firstPlatform = module.DefaultWindowsPlatforms.Split(',').FirstOrDefault(); break; case "MacOS": firstPlatform = module.DefaultMacOSPlatforms.Split(',').FirstOrDefault(); break; case "Linux": firstPlatform = module.DefaultLinuxPlatforms.Split(',').FirstOrDefault(); break; } if (firstPlatform != null) { // This will end up null if the first platform isn't supported // either and hence throw the right message. platform = module.NormalizePlatform(firstPlatform); } } if (platform == null) { Console.Error.WriteLine("The platform '" + originalPlatform + "' is not supported."); Console.Error.WriteLine("The following platforms are supported by this module:"); foreach ( var supportedPlatform in module.SupportedPlatforms.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()) .Where(x => !string.IsNullOrWhiteSpace(x))) { Console.Error.WriteLine(" * " + supportedPlatform); } ExecEnvironment.Exit(1); return(false); } var primaryPlatform = platform; // You can generate multiple targets by default by setting the <DefaultWindowsPlatforms> // <DefaultMacOSPlatforms> and <DefaultLinuxPlatforms> tags in Module.xml. Note that // synchronisation will only be done for the primary platform, as there is no correct // synchronisation behaviour when dealing with multiple C# projects. // // We only trigger this behaviour when the platform is omitted; if you explicitly // specify "Windows" on the command line, we'll only generate / resync / sync // the Windows platform. string multiplePlatforms = null; if (!platformSupplied) { switch (platform) { case "Windows": multiplePlatforms = module.DefaultWindowsPlatforms; break; case "MacOS": multiplePlatforms = module.DefaultMacOSPlatforms; break; case "Linux": multiplePlatforms = module.DefaultLinuxPlatforms; break; } } // If no overrides are set, just use the current platform. if (string.IsNullOrEmpty(multiplePlatforms)) { multiplePlatforms = platform; } // Resolve submodules as needed. this.m_PackageManager.ResolveAll(module, primaryPlatform); // You can configure the default action for Protobuild in their project // with the <DefaultAction> tag in Module.xml. If omitted, default to a resync. // Valid options for this tag are either "Generate", "Resync" or "Sync". // If the actions are "Resync" or "Sync", then we need to perform an initial // step against the primary platform. switch (action.ToLower()) { case "generate": if (!this.GenerateProjectsForPlatform(module, primaryPlatform, enabledServices, disabledServices, serviceSpecPath)) { return(false); } break; case "resync": if (!this.ResyncProjectsForPlatform(module, primaryPlatform, enabledServices, disabledServices, serviceSpecPath)) { return(false); } break; case "sync": return(this.SyncProjectsForPlatform(module, primaryPlatform)); case "clean": if (!this.CleanProjectsForPlatform(module, primaryPlatform)) { return(false); } break; default: Console.Error.WriteLine("Unknown option in <DefaultAction> tag of Module.xml. Defaulting to resync!"); return(this.ResyncProjectsForPlatform(module, primaryPlatform, enabledServices, disabledServices, serviceSpecPath)); } // Now iterate through the multiple platforms specified. var multiplePlatformsArray = multiplePlatforms.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()); foreach (var platformIter in multiplePlatformsArray) { if (platformIter == primaryPlatform) { // Already handled above. continue; } // Resolve submodules as needed. this.m_PackageManager.ResolveAll(module, platformIter); switch (action.ToLower()) { case "generate": case "resync": // We do a generate under resync mode since we only want the primary platform // to have synchronisation done (and it has had above). if (!this.GenerateProjectsForPlatform(module, platformIter, enabledServices, disabledServices, serviceSpecPath)) { return(false); } break; case "clean": if (!this.CleanProjectsForPlatform(module, platformIter)) { return(false); } break; default: throw new InvalidOperationException("Code should never reach this point"); } } // All the steps succeeded, so return true. return(true); }
/// <summary> /// Runs the instance of Protobuild.exe present in the module. /// </summary> /// <param name="args">The arguments to pass to Protobuild.</param> public void RunProtobuild(string args) { if (ExecEnvironment.RunProtobuildInProcess) { var old = Environment.CurrentDirectory; try { Environment.CurrentDirectory = this.Path; ExecEnvironment.InvokeSelf(args.Split(' ')); } finally { Environment.CurrentDirectory = old; } return; } var protobuildPath = System.IO.Path.Combine(this.Path, "Protobuild.exe"); try { var chmodStartInfo = new ProcessStartInfo { FileName = "chmod", Arguments = "a+x Protobuild.exe", WorkingDirectory = this.Path, CreateNoWindow = true, UseShellExecute = false }; Process.Start(chmodStartInfo); } catch (ExecEnvironment.SelfInvokeExitException) { throw; } catch { } if (File.Exists(protobuildPath)) { var pi = new ProcessStartInfo { FileName = protobuildPath, Arguments = args, WorkingDirectory = this.Path, CreateNoWindow = true, RedirectStandardError = true, RedirectStandardInput = true, RedirectStandardOutput = true, UseShellExecute = false }; var p = new Process { StartInfo = pi }; p.OutputDataReceived += (sender, eventArgs) => { if (!string.IsNullOrEmpty(eventArgs.Data)) { Console.WriteLine(eventArgs.Data); } }; p.ErrorDataReceived += (sender, eventArgs) => { if (!string.IsNullOrEmpty(eventArgs.Data)) { Console.Error.WriteLine(eventArgs.Data); } }; p.Start(); p.BeginOutputReadLine(); p.BeginErrorReadLine(); p.WaitForExit(); } }