Esempio n. 1
0
 static public bool IsServerAlive(int?port)
 {
     try
     {
         BuildServer.Request("-ping", port);
         return(true);
     }
     catch
     {
         return(false);
     }
 }
Esempio n. 2
0
        static public (string response, int exitCode) SendBuildRequest(string[] args, int?port)
        {
            int exitCode = 0;

            string get_response()
            {
                try
                {
                    // first arg is the compiler identifier: csc|vbc

                    string request  = string.Join('\n', args);
                    string response = BuildServer.Request(request, port);

                    var responseItems = response.Split(new char[] { '|' }, 2);
                    if (responseItems.Count() < 2)
                    {
                        exitCode = 1;
                        return("Build server output is in unexpected format. The compiler exit code is not available.\n" +
                               "Try to restart the build server with 'css -server:stop' followed by 'css -server:start'.");
                    }

                    exitCode = int.Parse(responseItems[0]);
                    return(responseItems[1]);
                }
                catch (Exception e)
                {
                    return(e.ToString());
                }
            }

            var response = get_response();

            var retry = 0;

            while (response.Contains("SocketException") && retry < 5)
            {
                Thread.Sleep(30);
                response = get_response();
            }

            return(response, exitCode);
        }
        CompilerResults CompileAssemblyFromFileBatch_with_Csc(CompilerParameters options, string[] fileNames)
        {
            if (fileNames.Any() && fileNames.First().GetExtension().SameAs(".vb"))
            {
                throw new CompilerException("Executing VB scripts is only supported on dotnet engine. Please either set it:" + NewLine +
                                            " - as global setting with [css -config:set:DefaultCompilerEngine=dotnet]" + NewLine +
                                            " - from CLI parameters with [css -ng:dotnet <scriupt.vb>]" + NewLine +
                                            " - from your VB script code with [' //css_ng dotnet]");
            }

            if (!Runtime.IsSdkInstalled())
            {
                Console.WriteLine("WARNING: .NET SDK is not installed. It is required for CS-Script to function properly.");
            }

            string projectName = fileNames.First().GetFileName();

            var engine_dir = this.GetType().Assembly.Location.GetDirName();
            var cache_dir  = CSExecutor.ScriptCacheDir; // C:\Users\user\AppData\Local\Temp\csscript.core\cache\1822444284
            var build_dir  = cache_dir.PathJoin(".build", projectName);

            build_dir.DeleteDir(handleExceptions: true)
            .EnsureDir();

            var sources = new List <string>();

            fileNames.ForEach((string source) =>
            {
                // As per dotnet.exe v2.1.26216.3 the pdb get generated as PortablePDB, which is the only format that is supported
                // by both .NET debugger (VS) and .NET Core debugger (VSCode).

                // However PortablePDB does not store the full source path but file name only (at least for now). It works fine in typical
                // .Core scenario where the all sources are in the root directory but if they are not (e.g. scripting or desktop app) then
                // debugger cannot resolve sources without user input.

                // The only solution (ugly one) is to inject the full file path at startup with #line directive

                var new_file   = build_dir.PathJoin(source.GetFileName());
                var sourceText = $"#line 1 \"{source}\"{Environment.NewLine}" + File.ReadAllText(source);
                File.WriteAllText(new_file, sourceText);
                sources.Add(new_file);
            });

            var ref_assemblies = options.ReferencedAssemblies.Where(x => !x.IsSharedAssembly())
                                 .Where(Path.IsPathRooted)
                                 .Where(asm => asm.GetDirName() != engine_dir)
                                 .ToList();

            if (CSExecutor.options.enableDbgPrint)
            {
                ref_assemblies.Add(Assembly.GetExecutingAssembly().Location());
            }

            var refs     = new StringBuilder();
            var assembly = build_dir.PathJoin(projectName + ".dll");

            var result = new CompilerResults();

            var needCompileXaml = fileNames.Any(x => x.EndsWith(".xaml", OrdinalIgnoreCase));

            if (needCompileXaml)
            {
                result.Errors.Add(new CompilerError
                {
                    ErrorText = $"In order to compile XAML you need to use 'dotnet' compiler. " + NewLine +
                                $"You can set it by any of this methods:" + NewLine +
                                $"- for a specific script from code with \"//css_engine dotnet\" directive" + NewLine +
                                $"- for the process with a CLI argument \"dotnet .{Path.DirectorySeparatorChar}cscs.dll -engine:dotnet <script>\"" + NewLine +
                                $"- globally as a config value \"dotnet .{Path.DirectorySeparatorChar}cscs.dll -config:set:DefaultCompilerEngine=dotnet\""
                });;
                return(result);
            }

            if (!options.GenerateExecutable || !Runtime.IsCore || DefaultCompilerRuntime == DefaultCompilerRuntime.Standard)
            {
                // todo
                // nothing for now
            }

            //----------------------------

            //pseudo-gac as .NET core does not support GAC but rather common assemblies.
            // C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.4
            var gac = typeof(string).Assembly.Location.GetDirName();

            // C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\5.0.4
            string gac2 = (options.AppType == "Web" ? gac.Replace("Microsoft.NETCore.App", "Microsoft.AspNetCore.App") : null);

            var refs_args   = new List <string>();
            var source_args = new List <string>();
            var common_args = new List <string>();

            common_args.Add("/utf8output");
            common_args.Add("/nostdlib+");

            common_args.Add("/t:exe"); // need always build exe so "top-class" feature is supported even when building dlls

            if (options.IncludeDebugInformation)
            {
                common_args.Add("/debug:portable");  // on .net full it is "/debug+"
            }
            if (options.CompilerOptions.HasText())
            {
                common_args.Add(options.CompilerOptions);
            }

            common_args.Add("-define:TRACE;NETCORE;CS_SCRIPT");

            var gac_asms = Directory.GetFiles(gac, "System.*.dll").ToList();

            gac_asms.AddRange(Directory.GetFiles(gac, "netstandard.dll"));
            // Microsoft.DiaSymReader.Native.amd64.dll is a native dll
            gac_asms.AddRange(Directory.GetFiles(gac, "Microsoft.*.dll").Where(x => !x.Contains("Native")));

            if (gac2.HasText())
            {
                gac_asms.AddRange(Directory.GetFiles(gac2, "Microsoft.*.dll").Where(x => !x.Contains("Native")));
            }

            foreach (string file in gac_asms.Concat(ref_assemblies))
            {
                refs_args.Add($"/r:\"{file}\"");
            }

            foreach (string file in sources)
            {
                source_args.Add($"\"{file}\"");
            }

            // running build server on Linux is problematic as if it is started from here it will be killed when the
            // parent process (this) exits

            // bool compile_on_server = Runtime.IsWin;
            bool   compile_on_server = true;
            string cmd     = "";
            string std_err = "";

            Profiler.get("compiler").Start();
            if (compile_on_server)
            {
                compile_on_server = Globals.BuildServerIsDeployed;
            }

            var cmpl_cmd = "";

            if (compile_on_server)
            {
                Profiler.EngineContext = "Building with csc engine server (Build server)...";

                // using sockets directly

                var compiler = $"{sources.FirstOrDefault()?.GetExtension().Trim('.')}c";

                var request = $@"{compiler} {common_args.JoinBy(" ")}  /out:""{assembly}"" {refs_args.JoinBy(" ")} {source_args.JoinBy(" ")}"
                              .SplitCommandLine();

                cmpl_cmd = request.JoinBy(" ");
                // ensure server running
                // it will gracefully exit if another instance is running
                var startBuildServerCommand = $"\"{Globals.build_server}\" -listen -port:{BuildServer.serverPort} -csc:\"{Globals.csc}\"";

                dotnet.RunAsync(startBuildServerCommand);
                Thread.Sleep(30);

                (string response, int exitCode) = BuildServer.SendBuildRequest(request, BuildServer.serverPort);

                bool buildServerNotRunning() => response.GetLines()
                .FirstOrDefault()?
                .Contains("System.Net.Internals.SocketExceptionFactory+ExtendedSocketException (10061)")
                == true;

                for (int i = 0; i < 10 && buildServerNotRunning(); i++)
                {
                    Thread.Sleep(100);
                    (response, exitCode) = BuildServer.SendBuildRequest(request, BuildServer.serverPort);
                }

                if (buildServerNotRunning())
                {
                    throw new CompilerException("CS-Script build server is not running:\n" +
                                                $"Try to start server manually with 'cscs -server:start'");
                }

                result.NativeCompilerReturnValue = exitCode;
                result.Output.AddRange(response.GetLines());
            }
            else
            {
                Profiler.EngineContext = "Building with local csc engine...";
                cmd      = $@"""{Globals.GetCompilerFor(sources.FirstOrDefault())}"" {common_args.JoinBy(" ")} /out:""{assembly}"" {refs_args.JoinBy(" ")} {source_args.JoinBy(" ")}";
                cmpl_cmd = cmd;
                result.NativeCompilerReturnValue = dotnet.Run(cmd, build_dir, x => result.Output.Add(x), x => std_err += x);
            }

            if (std_err.HasText())
            {
                result.Output.Add($"cmpl_stde: {std_err}");
            }

            Profiler.get("compiler").Stop();

            if (CSExecutor.options.verbose)
            {
                if (Environment.GetEnvironmentVariable("echo_compiler_cli") == null)
                {
                    Console.WriteLine("    csc.dll: " + Profiler.get("compiler").Elapsed);
                }
                else
                {
                    // File.WriteAllTe();
                    Console.WriteLine("> ================");
                    Console.WriteLine("csc.dll run: ");
                    Console.WriteLine($"  current_dir: {build_dir}");
                    Console.WriteLine($"  cmd: dotnet \"{Globals.csc}\" {cmpl_cmd}");
                    Console.WriteLine($"  output: {NewLine}{result.Output.JoinBy(NewLine)}");
                    Console.WriteLine("> ================");
                }
            }

            result.ProcessErrors();

            result.Errors
            .ForEach(x =>
            {
                // by default x.FileName is a file name only
                x.FileName = fileNames.FirstOrDefault(f => f.EndsWith(x.FileName ?? "")) ?? x.FileName;
            });

            if (result.NativeCompilerReturnValue == 0 && File.Exists(assembly))
            {
                if (Runtime.IsLinux)
                {
                    result.PathToAssembly = options.OutputAssembly;
                    File.Copy(assembly, result.PathToAssembly, true);
                }
                else
                {
                    result.PathToAssembly = options.OutputAssembly;
                    try { File.Copy(assembly, result.PathToAssembly, true); } catch { }
                }

                if (options.GenerateExecutable)
                {
                    var runtimeconfig = "{'runtimeOptions': {'framework': {'name': 'Microsoft.NETCore.App', 'version': '{version}'}}}"
                                        .Replace("'", "\"")
                                        .Replace("{version}", Environment.Version.ToString());

                    File.WriteAllText(result.PathToAssembly.ChangeExtension(".runtimeconfig.json"), runtimeconfig);
                    try
                    {
                        // CSUtils.
                        if (Runtime.IsLinux)
                        {
                            File.Copy(assembly, result.PathToAssembly.RemoveAssemblyExtension(), true);
                        }
                        else
                        {
                            File.Copy(assembly, result.PathToAssembly.ChangeExtension(".exe"), true);
                        }
                    }
                    catch { }
                }
                else
                {
                    File.Copy(assembly, result.PathToAssembly, true);
                }

                if (options.IncludeDebugInformation)
                {
                    File.Copy(assembly.ChangeExtension(".pdb"),
                              result.PathToAssembly.ChangeExtension(".pdb"),
                              true);
                }
            }
            else
            {
                if (result.Errors.Any(x => x.ErrorNumber == "CS2012") && Runtime.IsLinux && compile_on_server)
                {
                    // When running on Linux as sudo CS-Script creates temp folders (e.g. cache) that automatically
                    // get write-protected if accessed by not sudo-process. On Windows these folders are always not protected.
                    // Meaning that if the build server is running as non root (sudo) it will fail to place the output to these folders.
                    // The solution is to either restart build server as sudo or use dotnet engine as it always starts and stops with the
                    // cscs.exe executable and always inherits its root context.

                    var outputDir = assembly.GetDirName();

                    bool serverCanWrite = BuildServer.Request($"-is_writable_dir:{outputDir}", BuildServer.serverPort) == true.ToString();
                    bool hostCanWrite   = outputDir.IsWritable();

                    if (hostCanWrite && !serverCanWrite)
                    {
                        result.Errors.Add(new CompilerError
                        {
                            ErrorText = "The build server have less permissions to write to the temporary output directories " +
                                        "than host process (cs-script process). " + NewLine +
                                        "permissions comparing to the build server. " + NewLine + NewLine +
                                        "If you are running cs-script with root privileges you may need to " +
                                        "restart the build server with root privileges too:" + NewLine + NewLine +
                                        "  sudo css -server:restart" + NewLine + NewLine +
                                        "Alternatively you can switch to dotnet engine, which is always " +
                                        "aligned with the script engine permissions context. " + NewLine +
                                        "(e.g. `cscs -ng:dotnet <script_path>`)"
                        });
                    }
                }

                if (result.Errors.IsEmpty())
                {
                    // unknown error; e.g. invalid compiler params
                    result.Errors.Add(new CompilerError {
                        ErrorText = "Unknown compiler error"
                    });
                }
            }

            build_dir.DeleteDir(handleExceptions: true);

            return(result);
        }