Clang NSTask with streams

807 views Asked by At

Never-mind all the "why?","useless?", and "don't bother" comments. I want to compile a program inside another program using clang. I can create the NSTask and set up the arguments and it will work if the file exists, (ie. no stream), and writes to a physical file. I haven't been able to get what I would really like which is to use streams for both input and output. I know that both clang and gcc allow for compiling stdin if you use the -xc and - options but am unable to implement that feature using pipes. I am also not sure how to redirect clang's output to a file handle or stream.

Here is the code I have that compiles it and generates the correct output in outfile

task = [[NSTask alloc] init];

NSPipe* outputPipe = [[NSPipe alloc] init];
[task setStandardOutput:outputPipe ];
[task setStandardError: [task standardOutput]];

NSPipe* inPipe = [NSPipe pipe];
[task setStandardInput:inPipe];


[task setLaunchPath:@"/usr/bin/clang"];
NSString* outfile= [NSString stringWithFormat:@"%@.out",[[filename lastPathComponent] stringByDeletingPathExtension]];

//[data writeToFile:@"file.c" atomically:YES];
[task setArguments:[NSArray arrayWithObjects:filename,@"-S",@"-o",outfile,nil]];


[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(getData:) 
                                             name: NSFileHandleReadCompletionNotification 
                                           object: [[task standardOutput] fileHandleForReading]];

[[[task standardOutput] fileHandleForReading] readInBackgroundAndNotify];

[task launch];

I have tried using this for the input stream:

/* on pipe creation*/
dup2([[inPipe fileHandleForReading] fileDescriptor], STDIN_FILENO);
NSFileHandle* curInputHandle = [inPipe fileHandleForWriting];
/* tried before launch and after, no output just sits */ 
[curInputHandle writeData:[NSData dataWithContentsOfFile:filename]];

Sometimes, I assume when the pipe closes while the NSTask still in existance the output file is created and will run. This makes me think that clang is just waiting for stdin to close. Is there a way to close the pipe when the data has been read?

For output I have tried to use NSPipe's fileHandleForWriting as the parameter of -o, That gives an error of [NSConcretePipe fileSystemRepresentation] unrecognized selector. I have tried creating a file handle with the file descriptor of stdout to the same error. I don't know of any command line argument that redirects it. I've tried using | to redirect but haven't been able to get it to work. If there is any unix magic to redirect it I can dup stdout to anywhere I want.

So is there any way to close a pipe when all the data it is read? And Redirect clangs output? If there is any other way to accomplish the same thing easier or cleaner I am open to any implementation. Any help on these two items would be so great.

1

There are 1 answers

0
CRD On BEST ANSWER

It is not clear to me what your problem is or what you've tried. However, if you are going to read the output from a pipe on your main thread using notifications and wish to also write to a pipe one option is to write to the pipe in another thread. The code below, based on your code, does this using GCD. For simplicity in this example the binary is deposited in /tmp:

// send a simple program to clang using a GCD task
- (void)provideStdin:(NSFileHandle *)stdinHandle
{
   dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
   dispatch_async(aQueue, ^{
      [stdinHandle writeData:[@"int main(int argc, char **argv)\n" dataUsingEncoding:NSUTF8StringEncoding]];
      [stdinHandle writeData:[@"{\n" dataUsingEncoding:NSUTF8StringEncoding]];
      [stdinHandle writeData:[@"   write(1, \"hello\\n\", 6);\n" dataUsingEncoding:NSUTF8StringEncoding]];
      [stdinHandle writeData:[@"}\n" dataUsingEncoding:NSUTF8StringEncoding]];
      [stdinHandle closeFile];   // sent the code, close the file (pipe in this case)
   });
}

// read the output from clang and dump to console
- (void) getData:(NSNotification *)notifcation
{
   NSData *dataRead = [[notifcation userInfo] objectForKey:NSFileHandleNotificationDataItem];
   NSString *textRead = [[NSString alloc] initWithData:dataRead encoding:NSUTF8StringEncoding];
   NSLog(@"read %3ld: %@", (long)[textRead length], textRead);
}

// invoke clang using an NSTask, reading output via notifications
// and providing input via an async GCD task
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
   NSTask *task = [NSTask new];

   NSPipe *outputPipe = [NSPipe new];
   [task setStandardOutput:outputPipe];
   [task setStandardError:outputPipe];
   NSFileHandle *outputHandle = [outputPipe fileHandleForReading];

   NSPipe* inPipe = [NSPipe pipe];
   [task setStandardInput:inPipe];

   [task setLaunchPath:@"/usr/bin/clang"];

   [task setArguments:[NSArray arrayWithObjects:@"-o", @"/tmp/clang.out", @"-xc",@"-",nil]];

   [[NSNotificationCenter defaultCenter] addObserver:self 
                                            selector:@selector(getData:) 
                                                name:NSFileHandleReadCompletionNotification 
                                              object:outputHandle];

   [outputHandle readInBackgroundAndNotify];

   [task launch];
   [self provideStdin:[inPipe fileHandleForWriting]];
}