tyrrrz / cliwrap Goto Github PK
View Code? Open in Web Editor NEWLibrary for running command-line processes
License: MIT License
Library for running command-line processes
License: MIT License
When I launch an app with ExecuteAndForget and then I close that app, this exception is thrown:
System.ObjectDisposedException: 'The semaphore has been disposed.'
Hi, thank you for your work here. I've enjoyed using it for over a year now.
I have generally observed no issues at all with your library around clean-up, signal management, output redirection, native handles, etc.
But the cancellations are often not picked up until after the process has exited and well beyond the original time of cancellation. So I am guessing that's this location:
Line 302 in 37da96b
The output callbacks keep getting invoked after cancellation and while the process is still running. That's what I'd expect would happen because we don't want to lose process output, but I thought I'd mention it.
This particular invocation is a build script that starts processes of its own (npm, etc.).
Do you have any suspicion what might cause this behavior? Is the cancellation callback guaranteed to get an execution slot or could it be stuck waiting for the ExecuteAsync
task to yield control?
I noticed that your TryKill
code is relatively simple and doesn't look at exceptions and other outcomes too hard . May I suggest additions of the following nature? This is taken from some process management code I wrote at some point. Not sure how the API surface maps to what's available on .NET Standard, but I think the general ideas apply.
The important idea being: Check if the termination succeeded or not. And if not handle it in some way.
bool closed;
try
{
if (!p.HasExited)
{
p.CloseMainWindow();
closed = p.WaitForExit(2000);
}
else
{
closed = true;
}
}
catch
{
closed = false;
}
if (!closed)
{
try
{
if (!p.HasExited)
{
p.Kill();
if (!p.WaitForExit(10000))
{
throw new SomeException(
$"Most likely failed to terminate process (process ID {processId})'. Termination reason: {reason}. Started with:\n{call}"
);
}
}
}
catch (InvalidOperationException) when (process.HasExited)
{
}
catch (Exception ex)
{
// ?
}
}
Setup:
CliWrap 2.3.0 in a NetCoreApp 2.2 project running on Windows directly as a Kestrel host.
// in this instance: powershell -File ".\build_client.ps1"
var cli = Cli.Wrap(executable)
.EnableExitCodeValidation(isEnabled: false)
.EnableStandardErrorValidation(isEnabled: false)
.SetWorkingDirectory(workingDirectory.FullName)
.SetArguments(command.Arguments ?? "")
.SetCancellationToken(cancellationToken);
cli
.SetStandardOutputCallback(async stdOutLine =>
{
if (httpTracing)
{
await logAsync(stdOutLine);
}
lock (writeLock1)
{
// StreamWriter backed by a file stream
writerStdOut.WriteLine(GetLine(stdOutLine));
}
})
.SetStandardErrorCallback(async stdErrLine =>
{
if (httpTracing)
{
await logAsync(stdErrLine);
}
lock (writeLock2)
{
// StreamWriter backed by a file stream
writerStdErr.WriteLine(GetLine(stdErrLine));
}
});
output = await cli.ExecuteAsync();
The current cancellation token kills that application which is needed in some cases but it would also be great to have one that could close the application so the application has time to close gracefully and cleanup. I have a few processes there it would be ideal that i could close them and only kill them as a last resort if the close does not work after a short period of time.
I'm getting System.InvalidOperationException
while trying to start the process.
System.InvalidOperationException: No process is associated with this object.
at System.Diagnostics.Process.EnsureState(State state)
at System.Diagnostics.Process.get_HasExited()
at CliWrap.Internal.ProcessEx.Dispose()
at CliWrap.Command.ExecuteAsync(ProcessEx process, CancellationToken cancellationToken)
What it actually means is that targetFilePath
pointed to non-existing exe and the process didn't even start. Which is fine, but it throws an exception in wrong place and the exception message is misleading.
Could we have a check whether the process actually started?
It looks like the Wrap method on the ICli interface is missing.
It is not possible to do DI or mocking on the Wrap method via interface.
Hello,
In our CLI program, we are invoking git mergetool
on the user's behalf, and we want to pipe input and output unaltered between the parent process, and the target process (git) for the duration of the target command execution.
We have tried various variations of the following:
await using var stdIn = Console.OpenStandardInput();
await using var stdOut = Console.OpenStandardOutput();
await using var stdErr = Console.OpenStandardError();
var cmd =
Cli.Wrap("git")
.WithWorkingDirectory(@"C:\SomeRepository")
.WithArguments("mergetool")
.WithStandardInputPipe(PipeSource.FromStream(stdIn))
.WithStandardOutputPipe(PipeTarget.ToStream(stdOut))
.WithStandardErrorPipe(PipeTarget.ToStream(stdErr));
var result = await cmd.ExecuteAsync();
The piping of stdout
and stderr
seem to work as expected. However, input from stdin
does not seem to get passed to the target process, because it does absolutely nothing in response to the input while prompting.
What are we doing wrong? Any advice would be greatly appreciated.
I am trying to achieve the following pipe with CliWrap:
ffmpeg.exe -y -i "C:\input.mp4" -pix_fmt yuv420p -f yuv4mpegpipe - | aomenc.exe -v -w 1920 -h 1080 --passes=2 --pass=1 --fpf=".\out.stats" -t 1 --cq-level=30 --end-usage=q -o ".\out.ivf" -
This command works as it should in Command Prompt and PowerShell.
To do this, I am using CliWrap like so:
var ffProcess = Cli.Wrap( "C:\\ffmpeg.exe" ).WithArguments( ffArgs );
var aomProcess = Cli.Wrap( "C:\\aomenc.exe" ).WithArguments( aomArgs );
await ( ffProcess | aomProcess ).ExecuteBufferedAsync();
This launches the processes, but seems to just sit there until terminated. When launched manually, these two processes take up 50% CPU, but with CliWrap, they just sit there at 0%. This happens with both ExecuteAsync()
and ExecuteBufferedAsync()
.
Ideally, I would like to use ListenAsync()
so that I can read from the StdErr of aomenc.exe
and parse the progress it outputs, but that doesn't seem to work either.
I've transformed some Process.Start()
calls into Cli.Wrap
calls. I've noticed i have to get pretty funky with quotation marks to make it work, at least with one specific application.
Previously i used ProcessStartInfo.ArgumentList
(which i guess was introduced with netstandard?).
For one process all i had to do is cli.SetArguments(string.Join(" ", argumentList))
and it worked.
For the other, problematic one i had to try a lot and ended up with a) putting almost (!) every argument set in quotation marks and b) also triple-quotation marks around a "string".
"\"-D RENDER=\"\"\"SINGLE\"\"\"\""
what previously was just "-D RENDER=\"SINGLE\""
.
The argument parser in the other application may be buggy. But still, i don't know why it worked with ProcessStartInfo.ArgumentList
before.
Looking into your implementation i see you're using "plain old" ProcessStartInfo.Arguments
. Maybe using the ProcessStartInfo.ArgumentList
and adding an API like ICli.AddArgument
would help for these cases.
Example of binary pipeline stream:
https://gist.github.com/SlowLogicBoy/45f25b3711c341ba974fe22f04f71f4a
Multiple pipelines example:
https://gist.github.com/SlowLogicBoy/63037d655cf8c667ae09ccebcf1164aa
Здравствуйте. С помощью вашей библиотеки я хочу выполнить запросы в консоли cmd. Но у меня не выполняются команды.
Вот пример кода:
string cmd = "ipconfig";
var result = Cli.Wrap("cmd.exe").SetArguments(cmd).Execute();
Console.WriteLine(result.StandardOutput);
Console.ReadKey();
Я хочу выполнить команду ipconfig, но результат работы я не получаю.
И поправьте меня, если я делаю что-то не так.
It would be nice to automatically kill all children if the parent is dying. Life as an orphan isn't a life worth living.
A tiny improvement would be to add [DebuggerDisplay("{Text}")]
to StandardOutputCommandEvent
and StandardErrorCommandEvent
. This way when you're looking at command outputs in the debugger you'd see their contents right away, not just after opening the Text
property.
What do you think? I can submit a PR.
Hey, any chance to release support for old netframework versions for tools like installers that need to aim for atleast net3.5 that comes by default with windows 7?
It doesn't have to have async methods, so you can reduce the overhead with BCL libs.
Regards, Eli.
Hi there! Thanks for this greaty utility! It makes my life a lot easier :)
Is there a chance to get some after process Start Callback in order to get the process Id right after the start? I'd do a PR if desired :)
Hi again Alexey,
Please, may you suggest an approach using CliWrap to parse the status as show the application below:
Such status will be show in a WinForm.
I tried something similar to the following method
public async Task I_can_execute_a_command_that_pipes_its_stdout_into_an_async_delegate()
at PipingSpec.cs, but I just get the calling when there is a new line, at the end of the status, i.e. 100%.
Any guidance on which approach use is appreciated.
Thanks
Create a simple console app that prints one, long, continuous string:
class Program
{
static void Main(string[] args)
{
Console.Out.Write(new String('x', 100000));
}
}
Now, create two unit tests:
[TestFixture]
public class Tests
{
[Test]
public async Task Test1()
{
var outputBuilder = new StringBuilder();
await Cli.Wrap(@"<pathToTestApp>")
.WithStandardOutputPipe(PipeTarget.ToStringBuilder(outputBuilder))
.ExecuteAsync();
var output = outputBuilder.ToString();
Assert.False(output.Contains(Environment.NewLine));
}
[Test]
public async Task Test2()
{
var outputBuilder = new StringBuilder();
await Cli.Wrap(@"<pathToTestApp>")
.WithStandardOutputPipe(PipeTarget.ToDelegate(line => {outputBuilder.AppendLine(line); }))
.ExecuteAsync();
var output = outputBuilder.ToString();
Assert.False(output.Contains(Environment.NewLine));
}
}
The first will pass, as expected. The second will fail.
It appears that the delegate PipeTarget
is splitting a single, long line in to multiple lines. We're using the delegate target because we want to do some other things to the output than just writing to the StringBuilder (i.e. logging).
Hi there,
Thanks for this great lib! I'd like to have an option to kill the entire process tree. I'd do a PR as I need it
Sorry for beginner question, I used this library to create an automated Build (msbuild.exe), my question is let's say for some reason it's taking too long, and I want to cancel the process. How do I do that?
My current app is created using SignalR so every user can see the terminal output on real time.
var cli = Cli.Wrap(command).WithValidation(CommandResultValidation.None).WithArguments(arguments);
var output = "";
await foreach (var cmdEvent in cli.ListenAsync())
{
switch (cmdEvent)
{
case StartedCommandEvent started:
output = "ProcessID : " + started.ProcessId;
break;
case StandardOutputCommandEvent stdOut:
output = stdOut.Text;
break;
case StandardErrorCommandEvent stdErr:
output = stdErr.Text;
break;
case ExitedCommandEvent exited:
output = "ExitCode : " + exited.ExitCode;
break;
}
//send to all client
await Clients.All.SendAsync("BuildOutput", output);
}
Hi
I'm using CliWrap to running multiple ffmpeg (about 300 in one moment) processes all the time (each process lives for like 5 seconds). Recently i got this exception on my global error handler dispite the fact that every call to Cli.Execute
is wrapped by try-catch. Any idea why this might be the case? It's like super rare, but still..
System.AggregateException: One or more errors occurred. ---> System.ComponentModel.Win32Exception: Access is denied
at System.Diagnostics.Process.Kill()
at CliWrap.Internal.Extensions.KillIfRunning(Process process)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.CancellationCallbackInfo.ExecuteCallback()
at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
--- End of inner exception stack trace ---
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
at System.Threading.CancellationTokenSource.NotifyCancellation(Boolean throwOnFirstException)
at System.Threading.CancellationTokenSource.TimerCallbackLogic(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.TimerQueueTimer.CallCallback()
at System.Threading.TimerQueueTimer.Fire()
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
---> (Inner Exception #0) System.ComponentModel.Win32Exception (0x80004005): Access is denied
at System.Diagnostics.Process.Kill()
at CliWrap.Internal.Extensions.KillIfRunning(Process process)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.CancellationCallbackInfo.ExecuteCallback()
at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)<---
System.ComponentModel.Win32Exception (0x80004005): Access is denied
at System.Diagnostics.Process.Kill()
at CliWrap.Internal.Extensions.KillIfRunning(Process process)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.CancellationCallbackInfo.ExecuteCallback()
at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
Title of the issue is self-explanatory: as a suggestion, stdin
might be more flexibly represented by having CliWrap provide an IObserver<String>
. In this vein, your library would then expose stdout
and stderr
as each an instance of the push-oriented interface IObservable<String>
.
These changes would allow for better integration with other standard techniques, tools, and libraries such as Reactive Extensions.
I have been testing a Windows Service that uses CliWrap and on my DEV machine it works fine.
However, when I try copying the binaries (including all the assemblies that VS2019 included in the output), I get this stack trace:
System.MissingMethodException: Method not found: 'System.Collections.Generic.IDictionary
2<System.String,System.String> System.Diagnostics.ProcessStartInfo.get_Environment()'.
at CliWrap.Command.GetStartInfo()
at CliWrap.Command.ExecuteAsync(CancellationToken cancellationToken)`
Can you help me figure out what dependency CliWrap is looking for? I can't find anything missing as far as I've looked!
FWIW: Building/Targeting .Net 4.6.1
Thanks.
Youtube.Converter fails in Xamarin.mac, it's not a runtime issue with code, it appears to be something with mono.
{System.InvalidProgramException: Invalid IL code in YoutubeExplode.Converter.Internal.FFmpeg/<ConvertAsync>d__2:MoveNext (): IL_01c0: stloc.2
at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine] (TStateMachine& stateMachine) [0x0002c] in /Library/Frameworks/Xamarin.Mac.framework/Versions/Current/src/Xamarin.Mac/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:316
at YoutubeExplode.Converter.Internal.FFmpeg.ConvertAsync (System.String outputFilePath, System.Collections.Generic.IReadOnlyList`1[T] inputFilePaths, System.String format, System.String preset, System.Boolean avoidTranscoding, System.IProgress`1[T] progress, System.Threading.CancellationToken cancellationToken) [0x00048] in <941c5308801d480e8150dab71e27de93>:0
at YoutubeExplode.Converter.YoutubeConverter.DownloadAndProcessMediaStreamsAsync (System.Collections.Generic.IReadOnlyList`1[T] streamInfos, System.String filePath, System.String format, YoutubeExplode.Converter.ConversionPreset preset, System.IProgress`1[T] progress, System.Threading.CancellationToken cancellationToken) [0x002b5] in /YoutubeExplode.Converter/YoutubeExplode.Converter/YoutubeConverter.cs:91 }
The following code is what is causing the issue:
await Cli.Wrap(_ffmpegFilePath)
.WithArguments(arguments.Build())
.WithStandardErrorPipe(stdErrPipe)
.ExecuteAsync();
The constructor won't even get called because that code is there, replacing it with:
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = _ffmpegFilePath,
Arguments = arguments.Build(),
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
string result = process.StandardOutput.ReadToEnd();
process.WaitForExit();
The conversion will take place, its a very strange issue, I hoping a work around might be added to your library to make this work
Is it possible to answer multiple command prompts using CliWrap?
I'd like to automate configuring RClone but the challenge is that there are multiple questions in the setup and long delays in between each steps (while API calls are made in the background).
Obviously I can figure out the answers to all the prompts ahead of time, I'm just not sure how I'd accomplish triggering the prompt answers with CliWrap.
After looking for a good library doing basically what CliWrap does I think it's by far the best solution I've seen so far, thanks for the great work!
While reading some source code stumbled upon this line
private readonly TaskCompletionSource<object?> _exitTcs = new TaskCompletionSource<object?>();
and had to think of David Fowler's async guidance. I don't know if you considered this and I'm not at all deep enough into your code to know if it might not make sense in this case, but I thought I'd drop a note.
Hi, thanks for the library. I'm trying to use it to automate something.
I have a 'client' application that has the following code:
while (true)
{
try
{
switch (Console.ReadKey(true).Key)
{
}
} catch {}
}
Because of this, I get as fast as it can the following errors:
Out> 2020-04-15 16:44:12.9287 [1] ERROR Command.ProcessConsoleInput Error processing action System.InvalidOperationException: Cannot read keys when either application does not have a console or when console input has been redirected. Try Console.Read.
Out> at System.ConsolePal.ReadKey(Boolean intercept)
Out> at System.Console.ReadKey(Boolean intercept)
Out> at ClientApplication.Command.ProcessConsoleInput() in Command.cs:line 45
Can something be done by giving the client application a 'pipe' to read from?
The most beautiful solution would be if this could be redirected from the host application, thanks!
Hey, so I just updated CliWrap on my App and changed the usage to use SetArguments
.
I'm having trouble now as it throws an error on my ExecuteAsync()
line even though the command it executes works 100%. It's using ffmpeg to copy a video.
In the temp folder it outputs to, the file is there and works completely fine. Can't figure out whats erroring here
Error:
HResult=0x80131500
Message=Underlying process reported an error:
ffmpeg version N-90649-g9825f77ac7 Copyright (c) 2000-2018 the FFmpeg developers
built with gcc 7.3.0 (GCC)
configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-bzlib --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth
libavutil 56. 13.100 / 56. 13.100
libavcodec 58. 17.100 / 58. 17.100
libavformat 58. 11.101 / 58. 11.101
libavdevice 58. 2.100 / 58. 2.100
libavfilter 7. 14.100 / 7. 14.100
libswscale 5. 0.102 / 5. 0.102
libswresample 3. 0.101 / 3. 0.101
libpostproc 55. 0.100 / 55. 0.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'C:\Users\njord\Videos\nVidia Share\Rocket League\rage_quit.mp4':
Metadata:
major_brand : mp42
minor_version : 0
compatible_brands: isommp42
creation_time : 2018-09-29T10:20:37.000000Z
date : 2018
Duration: 00:02:00.23, start: 0.000000, bitrate: 46971 kb/s
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, smpte170m/smpte170m/bt470m), 1920x1080 [SAR 1:1 DAR 16:9], 46769 kb/s, 59.89 fps, 60 tbr, 90k tbn, 120 tbc (default)
Metadata:
creation_time : 2018-09-29T10:20:37.000000Z
handler_name : VideoHandle
Stream #0:1(und): Audio: aac (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 195 kb/s (default)
Metadata:
creation_time : 2018-09-29T10:20:37.000000Z
handler_name : SoundHandle
[mp4 @ 00000219fa0f44c0] track 1: codec frame size is not set
Output #0, mp4, to 'C:\Users\njord\Videos\temp\fbc9413e-a396-4f6b-8101-861cc592f16b.mp4':
Metadata:
major_brand : mp42
minor_version : 0
compatible_brands: isommp42
date : 2018
encoder : Lavf58.11.101
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, smpte170m/smpte170m/bt470m), 1920x1080 [SAR 1:1 DAR 16:9], q=2-31, 46769 kb/s, 59.89 fps, 60 tbr, 90k tbn, 90k tbc (default)
Metadata:
creation_time : 2018-09-29T10:20:37.000000Z
handler_name : VideoHandle
Stream #0:1(und): Audio: aac (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 195 kb/s (default)
Metadata:
creation_time : 2018-09-29T10:20:37.000000Z
handler_name : SoundHandle
Stream mapping:
Stream #0:0 -> #0:0 (copy)
Stream #0:1 -> #0:1 (copy)
Press [q] to stop, [?] for help
frame= 5810 fps=0.0 q=-1.0 size= 556800kB time=00:01:37.79 bitrate=46641.5kbits/s speed= 195x
frame= 7200 fps=0.0 q=-1.0 Lsize= 689458kB time=00:02:00.21 bitrate=46983.7kbits/s speed= 198x
video:686409kB audio:2871kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.025804%
Source=CliWrap
StackTrace:
at CliWrap.Cli.<ExecuteAsync>d__28.MoveNext()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at YouTubeTool.ViewModels.CutVideoViewModel.<Go>d__46.MoveNext() in C:\Source\GitHub\remiX-\YouTubeTool\src\ViewModels\CutVideoViewModel.cs:line 147```
I've been playing with this example:
https://github.com/Tyrrrz/CliWrap#executing-a-command-as-an-event-stream
And found out that is process exits with non-zero exit code, then exception is thrown and case ExitedCommandEvent exited:
is never executed.
Is it expected?
Hey,
Great library you have here. I would suggest you to keep some kind of changelog for users, so we can keep up with what is new. Probably simple CHANGELOG.md would be good enough
I'm using the ExecuteAsync()
in a start-function and call the cts.Cancel()
from a stop-function. The Cli wraps ffmpeg.exe and the hard Process.Kill()
prevents the application to finish recording or converting of videofiles.
Maybe you could add a flag like "UseSoftProcessKill" and send the CTRL+C Command this way:
public async Task StopScreenRecordAsync() { if (AttachConsole((uint)ffmpegProcess.Id)) { SetConsoleCtrlHandler(null, true); try { if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) { return; } while (!ffmpegProcess.HasExited) { await Task.Delay(200); } } finally { FreeConsole(); SetConsoleCtrlHandler(null, false); } } }
Thank you!
When I run git.exe
it successfully completes, but ExecutionOutput.StandardError
is still filled up with text and the ExecutionOutput.HasError
is true
. Is this a bug in CliWrap or is there something wrong with git.exe
?
How to reproduce:
Git-2.16.2-64-bit.exe
from https://git-scm.com/download/winC:\temp
folder exists.using (var cli = new Cli(@"C:\Program Files\Git\bin\git.exe", new CliSettings { WorkingDirectory = @"C:\temp" })) {
var output = await cli.ExecuteAsync("clone -b \"1.8.2\" --single-branch --depth 1 https://github.com/Tyrrrz/CliWrap.git");
var errortext = output.StandardError;
}
BTW, the StandardError contains this text:
Cloning into 'CliWrap'...
remote: Counting objects: 38, done.
remote: Compressing objects: 100% (34/34), done.
remote: Total 38 (delta 1), reused 18 (delta 0), pack-reused 0
Unpacking objects: 100% (38/38), done.
Note: checking out 'a4f337c2e4549ae5866656f8249cb21d51794f17'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b <new-branch-name>
Note: This is also an issue when running on Ubuntu 16.04.4
Not sure if this is a bug or by design. But even using the builder method produces the same results.
Hello.
The following code (contains RANDOM string):
var cmdTest = Cli.Wrap("myexe")
.WithArguments(a => a
.Add(new[]
{
"/superstring",
"ogq77a3auubgQaVHUXhKpVa*NR!YTsEjn8MA2^9$gZwb2D2z3EU^Fk7d8pS&psPg9F8M*jbfyCDWsewF$5osz9LmcsGUf5jV^@WarHuYPxZXN@GU5&AyhE!3t%W&f@ra"
}));
produces this command argument - no quotes around the superstring while I expect it to be escaped:
/superstring ogq77a3auubgQaVHUXhKpVa*NR!YTsEjn8MA2^9$gZwb2D2z3EU^Fk7d8pS&psPg9F8M*jbfyCDWsewF$5osz9LmcsGUf5jV^@WarHuYPxZXN@GU5&AyhE!3t%W&f@ra
But, if i want to add a single quotation mark with this code:
var cmdTest = Cli.Wrap("myexe")
.WithArguments(a => a
.Add(new[]
{
"/superstring",
"\"ogq77a3auubgQaVHUXhKpVa*NR!YTsEjn8MA2^9$gZwb2D2z3EU^Fk7d8pS&psPg9F8M*jbfyCDWsewF$5osz9LmcsGUf5jV^@WarHuYPxZXN@GU5&AyhE!3t%W&f@ra\""
}));
I get the following command arguments - too many quotes:
/superstring "\"ogq77a3auubgQaVHUXhKpVa*NR!YTsEjn8MA2^9$gZwb2D2z3EU^Fk7d8pS&psPg9F8M*jbfyCDWsewF$5osz9LmcsGUf5jV^@WarHuYPxZXN@GU5&AyhE!3t%W&f@ra\""
My way to overcome this is to create the arguments by myself with StringBuilder
and send it to the WithArguments
method.
Is there another way to solve it?
Thanks!
I use 7Zip with the -bsp1 option. This writes the progress as percentages, e.g.
1%
2%
3%
and so on.
But 7Zip always updates the line and writes no new line, so I cannot catch the percentage in CliWrap.
Is there any option to catch also stout lines that are permantly updates by the process?
First, very nice library!
Now to my problem, I use it to merge MKV files with mkvmerge (v46) but i have the problem that mkvmerge finishes but gives appenrently no exit code so my app hangs and i need to restart it.
How do you handle this case?
When ExecutionOutput.ThrowIfError()
is called, the exception message seen in the output log looks something like this:
Unhandled Exception: System.AggregateException: One or more errors occurred.
(Underlying process reported an error. Inspect [StandardError] property for more information.)
---> CliWrap.Exceptions.StandardErrorException:
Underlying process reported an error. Inspect [StandardError] property for more information.
This is "ok" in a development/debug environment, but won't be helpful in a production environment.
The ThrowIfError message should include the StandardError text.
Because of this line of code, it's currently impossible to use CliWrap to launch processes that need/allocate a PTY.
Help?
Let's suppose I have a long-running command-line app that exits on Ctrl+C. How would I model this with CliWrap? Startup is simple but once it runs how can I send it a Ctrl+C command? Would writing a line to a stream piped onto stdin work even after ExecuteAsync()
?
Thank you.
Hello,
Sorry for beginner question.
I would like to use this library to run an embedded resource executable. I cannot find any documentation or reference.
The idea is to hide the application from the user.
Thanks!
I would like to start a CSharp app via cmd which prints UTF-8 Text.
To work correctly I need to execute chcp 65001 first or start it with a non ANS OutputEncoding.
This would be possible by setting StandardOutputEncoding of the class ProcessStartInfo.
The construction of this class is unfortunately handled in a private method. Could you make these internals somehow available? Best would be something like an "OnAdjustProcessStartInfo" event.
I have seen that you have an overload ExecuteAsync which affects the interpretation of the binary output... But it does not "inform" the application about that encoding.
It would be helpful to have the capability of canceling long running process
Hello Sir, thank you for the great library.
It will be great to have an interface for Cli as it will be much more easy to create unit tests for classes which use Cli class.
E.g.
public interface ICli
{
string FilePath { get; }
CliSettings Settings { get; }
void CancelAll();
ExecutionOutput Execute(ExecutionInput input, CancellationToken cancellationToken = default(CancellationToken), IBufferHandler bufferHandler = null);
ExecutionOutput Execute(string arguments, CancellationToken cancellationToken = default(CancellationToken), IBufferHandler bufferHandler = null);
ExecutionOutput Execute(CancellationToken cancellationToken = default(CancellationToken), IBufferHandler bufferHandler = null);
void ExecuteAndForget(ExecutionInput input);
void ExecuteAndForget(string arguments);
void ExecuteAndForget();
Task<ExecutionOutput> ExecuteAsync(ExecutionInput input, CancellationToken cancellationToken = default(CancellationToken), IBufferHandler bufferHandler = null);
Task<ExecutionOutput> ExecuteAsync(string arguments, CancellationToken cancellationToken = default(CancellationToken), IBufferHandler bufferHandler = null);
Task<ExecutionOutput> ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken), IBufferHandler bufferHandler = null);
}
Regards,
System.ComponentModel.Win32Exception (0x80004005): Unable to retrieve the specified information about the process or thread. It may have exited or may be privileged.
at System.Diagnostics.Process.GetStat()
at System.Diagnostics.Process.get_StartTimeCore()
at System.Diagnostics.Process.get_StartTime()
at CliWrap.Cli.Execute(ExecutionInput input, CancellationToken cancellationToken, IBufferHandler bufferHandler)
I tried executing my app with sudo and without, gave file execute permission, I don't really know if it's my problem or the library, although I had it working while using the Process class.
Getting:
Unhandled Exception:
Unhandled Exception:System.ObjectDisposedException: The semaphore has been disposed.
at System.Threading.SemaphoreSlim.CheckDispose()
at System.Threading.SemaphoreSlim.Release(Int32 releaseCount)
at CliWrap.Internal.Signal.Release()
at CliWrap.Internal.CliProcess.<>c__DisplayClass24_0.<.ctor>b__2(Object sender, DataReceivedEventArgs args)
at System.Diagnostics.Process.ErrorReadNotifyUser(String data)
at System.Diagnostics.AsyncStreamReader.FlushMessageQueue()
at System.Diagnostics.AsyncStreamReader.ReadBuffer(IAsyncResult ar)
at System.IO.Stream.ReadWriteTask.InvokeAsyncCallback(Object completedTask)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.IO.Stream.ReadWriteTask.System.Threading.Tasks.ITaskCompletionAction.Invoke(Task completingTask)
at System.Threading.Tasks.Task.FinishContinuations()
at System.Threading.Tasks.Task.FinishStageThree()
at System.Threading.Tasks.Task.FinishStageTwo()
at System.Threading.Tasks.Task.Finish(Boolean bUserDelegateExecuted)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution)
at System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
It appears the events are still getting fired after the Signals are disposed.
The fix appears to be disconnecting the event handlers before disposing of the semaphores.
PR coming soon.
First of all, just wanted to say good job on this project, it's awesome and I will undoubtedly use it on any of my upcoming projects!
My request is to be able to set environment variables for the process. Exposing ProcessStartInfo.EnvironmentVariables
would be enough for the job.
We're using CliWrap to invoke R scripts for data modelling (R is a stats/data analysis language). I'm using the event stream usage example that you have posted on Github like this:
await cmd.Observe().ForEachAsync(cmdEvent => {
switch (cmdEvent) {
case StandardOutputCommandEvent stdOut:
sbOut.AppendLine($"{stdOut.Text}");
break;
case StandardErrorCommandEvent stdErr:
sbErr.AppendLine($"{stdErr.Text}");
break;
case ExitedCommandEvent exited:
//handle exit-code etc
break;
}
});
All I do for StandardOutputCommandEvent and StandardErrorCommandEvent is capture the text to 2 different string builders which I dump to a log periodically so we can actually see the script progress in real time. What seems to be happening though is that something locks up and the event stream stops capturing. So I get no further output and the ExitedCommandEvent never fires so it never runs my completion and cleanup code, to my program it just appears as if it is still running. The thing is, the script actually does complete it's execution because at the end it writes out several files and I've verified that those are being written even when the code above hangs.
The other clue is that when I was testing this, it was very sporadic: sometimes it would run to completion, sometimes it would hang-up. As best I can tell the issue seems to be with high volume messages in quick succession. When I would test with a small dataset, the RScript will rip though the numbers very quickly (only 1-2 mins) and since it is quite verbose in it's messaging, you'll get a lot of output happening quickly. But with the large datasets it takes R much longer to execute each step in the script so the messages come though much slower over 30-40 mins which doesn't seem to cause an issue. Not sure if this is a deadlocking issue, but I did see a mention of that in the last release notes. Thank you
Lets say I am doing an infinite ping.exe -t (actually something more like sqlite3.exe on a SQL script which does some pretty long queries or large updates/inserts) so:
"TokenSource?.Cancel(); Task?.Wait();" on Window Close/Exit doesn't take effect immediately but waits around and takes a long time if the currently executing query line is a long running one before finishing;
Whilst:
"if (this.Thread?.IsAlive == true) this.Thread?.Abort();" is both rather nasty and whilst it appearing immediate actually leaves a stray ping.exe (or sqlite3.exe) sub-process still up and running to cause havoc and locks or multi writer corruption on SQLite files for example.
I need to kill off the ping.exe (or sqlite3.exe) sub-process explicitly using its Process.Id so I am now compiling your code rather than using the DLL through NuGet so I am going to make the change here which I estimate at around a half dozen lines of code plus an external Kill(int Id) routine of my own.
Let me know if you would like the results to reincorporate back into your code case once I get this up tested and working and then I too can switch back to your newer NuGet DLL down the track!
Sound like a plan?
BTW: I moved to CliWrap from another library and this is the only thing missing. The Stdout and Stderr callbacks work like a treat: I couldn't see any easy way of doing line by line output without this and with any other library it has already made my life significantly easier. SUGGESTION: Make EnableExitCodeValidation and EnableStandardErrorValidation false by default as I started looking at MedallionShell in the beginning when I mistakenly thought that your library like the first I tried wasnt working and was giving me grief at first.
In addition to SetStandardOutputCallback
, it would be useful to have a method that returns an IAsyncEnumerable<string>
, to allow us to use await foreach
to iterate over stdout/stderr as it arrives.
More info in IAsyncEnumerable
: https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/generate-consume-asynchronous-stream
Implement "runas"
This happens sometimes, not sure why.
System.AggregateException: One or more errors occurred. ---> System.AggregateException: One or more errors occurred. ---> System.InvalidOperationException: An attempt was made to transition a task to a final state when it had already completed.
at System.Threading.Tasks.TaskCompletionSource`1.SetCanceled()
at CliWrap.Cli.<>c__DisplayClass22_1.<ExecuteAsync>b__3()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.CancellationCallbackInfo.ExecuteCallback()
at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
--- End of inner exception stack trace ---
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
at System.Threading.CancellationTokenSource.NotifyCancellation(Boolean throwOnFirstException)
at System.Threading.CancellationTokenSource.LinkedTokenCancelDelegate(Object source)
at System.Threading.CancellationCallbackInfo.ExecuteCallback()
at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
--- End of inner exception stack trace ---
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
at System.Threading.CancellationTokenSource.NotifyCancellation(Boolean throwOnFirstException)
at System.Threading.CancellationTokenSource.TimerCallbackLogic(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.TimerQueueTimer.CallCallback()
at System.Threading.TimerQueueTimer.Fire()
at System.Threading.TimerQueue.FireNextTimers()
---> (Inner Exception #0) System.AggregateException: One or more errors occurred. ---> System.InvalidOperationException: An attempt was made to transition a task to a final state when it had already completed.
at System.Threading.Tasks.TaskCompletionSource`1.SetCanceled()
at CliWrap.Cli.<>c__DisplayClass22_1.<ExecuteAsync>b__3()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.CancellationCallbackInfo.ExecuteCallback()
at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
--- End of inner exception stack trace ---
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
at System.Threading.CancellationTokenSource.NotifyCancellation(Boolean throwOnFirstException)
at System.Threading.CancellationTokenSource.LinkedTokenCancelDelegate(Object source)
at System.Threading.CancellationCallbackInfo.ExecuteCallback()
at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
---> (Inner Exception #0) System.InvalidOperationException: An attempt was made to transition a task to a final state when it had already completed.
at System.Threading.Tasks.TaskCompletionSource`1.SetCanceled()
at CliWrap.Cli.<>c__DisplayClass22_1.<ExecuteAsync>b__3()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.CancellationCallbackInfo.ExecuteCallback()
at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)<---
<---
System.AggregateException: One or more errors occurred. ---> System.InvalidOperationException: An attempt was made to transition a task to a final state when it had already completed.
at System.Threading.Tasks.TaskCompletionSource`1.SetCanceled()
at CliWrap.Cli.<>c__DisplayClass22_1.<ExecuteAsync>b__3()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.CancellationCallbackInfo.ExecuteCallback()
at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
--- End of inner exception stack trace ---
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
at System.Threading.CancellationTokenSource.NotifyCancellation(Boolean throwOnFirstException)
at System.Threading.CancellationTokenSource.LinkedTokenCancelDelegate(Object source)
at System.Threading.CancellationCallbackInfo.ExecuteCallback()
at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
---> (Inner Exception #0) System.InvalidOperationException: An attempt was made to transition a task to a final state when it had already completed.
at System.Threading.Tasks.TaskCompletionSource`1.SetCanceled()
at CliWrap.Cli.<>c__DisplayClass22_1.<ExecuteAsync>b__3()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.CancellationCallbackInfo.ExecuteCallback()
at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)<---
System.InvalidOperationException: An attempt was made to transition a task to a final state when it had already completed.
at System.Threading.Tasks.TaskCompletionSource`1.SetCanceled()
at CliWrap.Cli.<>c__DisplayClass22_1.<ExecuteAsync>b__3()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.CancellationCallbackInfo.ExecuteCallback()
at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
System.InvalidOperationException: An attempt was made to transition a task to a final state when it had already completed.
at System.Threading.Tasks.TaskCompletionSource`1.SetCanceled()
at CliWrap.Cli.<>c__DisplayClass22_1.<ExecuteAsync>b__3()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.CancellationCallbackInfo.ExecuteCallback()
at System.Threading.CancellationTokenSource.CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args)
at System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(Boolean throwOnFirstException)
also, i can be connected to other exception:
System.InvalidOperationException: An attempt was made to transition a task to a final state when it had already completed.
at System.Threading.Tasks.TaskCompletionSource`1.SetResult(TResult result)
at System.Diagnostics.Process.ErrorReadNotifyUser(String data)
at System.Diagnostics.AsyncStreamReader.FlushMessageQueue()
at System.Diagnostics.AsyncStreamReader.ReadBuffer(IAsyncResult ar)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.IO.Stream.ReadWriteTask.System.Threading.Tasks.ITaskCompletionAction.Invoke(Task completingTask)
at System.Threading.Tasks.Task.FinishContinuations()
at System.Threading.Tasks.Task.Finish(Boolean bUserDelegateExecuted)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution)
at System.Threading.ThreadPoolWorkQueue.Dispatch()
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.