mirror of
https://github.com/gnustep/apps-projectcenter.git
synced 2025-02-15 08:00:56 +00:00
542 lines
14 KiB
Objective-C
542 lines
14 KiB
Objective-C
/*
|
|
GNUstep ProjectCenter - http://www.gnustep.org/experience/ProjectCenter.html
|
|
|
|
Copyright (C) 2000-2020 Free Software Foundation
|
|
|
|
Authors: Philippe C.D. Robert
|
|
Serg Stoyan
|
|
Riccardo Mottola
|
|
|
|
This file is part of GNUstep.
|
|
|
|
This application is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This application is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public
|
|
License along with this library; if not, write to the Free
|
|
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
|
|
*/
|
|
|
|
#import <AppKit/AppKit.h>
|
|
|
|
#import <ProjectCenter/PCDefines.h>
|
|
#import <ProjectCenter/PCProject.h>
|
|
#import <ProjectCenter/PCProjectManager.h>
|
|
#import <ProjectCenter/PCProjectLauncher.h>
|
|
#import <ProjectCenter/PCBundleManager.h>
|
|
#import <ProjectCenter/PCButton.h>
|
|
#import <ProjectCenter/PCLogController.h>
|
|
#import <Protocols/Preferences.h>
|
|
|
|
|
|
#import "Modules/Preferences/Misc/PCMiscPrefs.h"
|
|
#import "Modules/Preferences/EditorFSC/PCEditorFSCPrefs.h"
|
|
|
|
|
|
#ifndef NOTIFICATION_CENTER
|
|
#define NOTIFICATION_CENTER [NSNotificationCenter defaultCenter]
|
|
#endif
|
|
|
|
#ifndef IMAGE
|
|
#define IMAGE(X) [NSImage imageNamed: X]
|
|
#endif
|
|
|
|
enum {
|
|
DEBUG_DEFAULT_TARGET = 1,
|
|
DEBUG_DEBUG_TARGET = 2
|
|
};
|
|
|
|
@protocol Terminal
|
|
|
|
- (BOOL)terminalRunProgram:(NSString *)path
|
|
withArguments:(NSArray *)args
|
|
inDirectory:(NSString *)directory
|
|
properties:(NSDictionary *)properties;
|
|
|
|
@end
|
|
|
|
@implementation PCProjectLauncher (UserInterface)
|
|
|
|
- (void)_createComponentView
|
|
{
|
|
NSScrollView *scrollView;
|
|
NSString *string;
|
|
NSAttributedString *attributedString;
|
|
|
|
componentView = [[NSBox alloc] initWithFrame:NSMakeRect(8,-1,464,322)];
|
|
[componentView setTitlePosition:NSNoTitle];
|
|
[componentView setBorderType:NSNoBorder];
|
|
[componentView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
|
|
[componentView setContentViewMargins: NSMakeSize(0.0,0.0)];
|
|
|
|
/*
|
|
* Top buttons
|
|
*/
|
|
runButton = [[PCButton alloc] initWithFrame: NSMakeRect(0,271,43,43)];
|
|
[runButton setTitle: @"Run"];
|
|
[runButton setImage: IMAGE(@"Run")];
|
|
[runButton setAlternateImage: IMAGE(@"Stop")];
|
|
[runButton setTarget: self];
|
|
[runButton setAction: @selector(run:)];
|
|
[runButton setAutoresizingMask: (NSViewMaxXMargin | NSViewMinYMargin)];
|
|
[runButton setButtonType: NSToggleButton];
|
|
[componentView addSubview: runButton];
|
|
RELEASE (runButton);
|
|
|
|
debugButton = [[PCButton alloc] initWithFrame: NSMakeRect(44,271,43,43)];
|
|
[debugButton setTitle: @"Debug"];
|
|
[debugButton setImage: IMAGE(@"Debug")];
|
|
[debugButton setAlternateImage: IMAGE(@"Stop")];
|
|
[debugButton setTarget: self];
|
|
[debugButton setAction: @selector(debug:)];
|
|
[debugButton setAutoresizingMask: (NSViewMaxXMargin | NSViewMinYMargin)];
|
|
[debugButton setButtonType: NSToggleButton];
|
|
[componentView addSubview: debugButton];
|
|
RELEASE (debugButton);
|
|
|
|
/*
|
|
*
|
|
*/
|
|
scrollView = [[NSScrollView alloc] initWithFrame:NSMakeRect (0,0,464,255)];
|
|
|
|
[scrollView setHasHorizontalScroller:NO];
|
|
[scrollView setHasVerticalScroller:YES];
|
|
[scrollView setBorderType: NSBezelBorder];
|
|
[scrollView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
|
|
|
|
stdOut=[[NSTextView alloc] initWithFrame:[[scrollView contentView] frame]];
|
|
|
|
[stdOut setMinSize: NSMakeSize(0, 0)];
|
|
[stdOut setMaxSize: NSMakeSize(1e7, 1e7)];
|
|
[stdOut setRichText:YES];
|
|
[stdOut setEditable:NO];
|
|
[stdOut setSelectable:YES];
|
|
[stdOut setVerticallyResizable: YES];
|
|
[stdOut setHorizontallyResizable: NO];
|
|
[stdOut setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable];
|
|
[[stdOut textContainer] setWidthTracksTextView:YES];
|
|
[[stdOut textContainer] setContainerSize:
|
|
NSMakeSize([stdOut frame].size.width, 1e7)];
|
|
|
|
// Font
|
|
string = @"=== Launcher ready ===";
|
|
attributedString =
|
|
[[NSAttributedString alloc] initWithString:string
|
|
attributes:textAttributes];
|
|
[[stdOut textStorage] setAttributedString:attributedString];
|
|
|
|
[scrollView setDocumentView:stdOut];
|
|
RELEASE (stdOut);
|
|
|
|
[componentView addSubview: scrollView];
|
|
RELEASE(scrollView);
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation PCProjectLauncher
|
|
|
|
- (id)initWithProject:(PCProject *)aProject
|
|
{
|
|
NSAssert (aProject, @"No project specified!");
|
|
|
|
if ((self = [super init]))
|
|
{
|
|
id <PCPreferences> prefs;
|
|
NSFont *font;
|
|
NSString *fontName;
|
|
CGFloat fontSize;
|
|
|
|
project = aProject;
|
|
|
|
prefs = [[project projectManager] prefController];
|
|
fontName = [prefs stringForKey:ConsoleFixedFont];
|
|
fontSize = [prefs floatForKey:ConsoleFixedFontSize];
|
|
font = [NSFont fontWithName:fontName size:fontSize];
|
|
if (font == nil)
|
|
font = [NSFont userFixedPitchFontOfSize:0];
|
|
|
|
textAttributes =
|
|
[NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
|
|
[textAttributes retain];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
#ifdef DEVELOPMENT
|
|
NSLog (@"PCProjectLauncher: dealloc");
|
|
#endif
|
|
RELEASE (componentView);
|
|
RELEASE (textAttributes);
|
|
RELEASE (debugger);
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
- (NSView *)componentView;
|
|
{
|
|
if (!componentView)
|
|
{
|
|
[self _createComponentView];
|
|
}
|
|
|
|
return componentView;
|
|
}
|
|
|
|
- (BOOL)isRunning
|
|
{
|
|
return _isRunning;
|
|
}
|
|
|
|
- (BOOL)isDebugging
|
|
{
|
|
return _isDebugging;
|
|
}
|
|
|
|
- (void)performRun
|
|
{
|
|
if (!_isRunning && !_isDebugging)
|
|
{
|
|
[runButton performClick:self];
|
|
}
|
|
}
|
|
|
|
- (void)performDebug
|
|
{
|
|
if (!_isRunning && !_isDebugging)
|
|
{
|
|
[debugButton performClick:self];
|
|
}
|
|
}
|
|
|
|
- (void)debug:(id)sender
|
|
{
|
|
NSString *executablePath;
|
|
NSString *gdbPath = nil;
|
|
NSFileManager *fm = [NSFileManager defaultManager];
|
|
PCBundleManager *bundleManager = [[project projectManager] bundleManager];
|
|
|
|
executablePath = [project projectPath];
|
|
|
|
if ([project isExecutable])
|
|
{
|
|
NSString *prjType;
|
|
|
|
prjType = [project projectTypeName];
|
|
if ([prjType isEqualToString: @"Application"])
|
|
{
|
|
/* MyApplication.app/MyApplication */
|
|
executablePath = [executablePath stringByAppendingPathComponent:[project projectName]];
|
|
executablePath = [executablePath stringByAppendingString:@".app"];
|
|
executablePath = [executablePath stringByAppendingPathComponent:[project projectName]];
|
|
}
|
|
else if ([prjType isEqualToString: @"Tool"])
|
|
{
|
|
/* obj/MyTool */
|
|
executablePath = [executablePath stringByAppendingPathComponent:@"obj"];
|
|
executablePath = [executablePath stringByAppendingPathComponent:[project projectName]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NSRunAlertPanel(@"Debug",
|
|
@"The project is not executable",
|
|
@"Close", nil, nil, nil);
|
|
[debugButton setState:NSOffState];
|
|
return;
|
|
}
|
|
|
|
#ifdef __MINGW__
|
|
/* On windows we need to check the .exe file */
|
|
if ([[executablePath pathExtension] length] == 0)
|
|
{
|
|
executablePath = [executablePath stringByAppendingPathExtension: @"exe"];
|
|
}
|
|
#endif
|
|
|
|
NSLog(@"debug executable launch path: %@", executablePath);
|
|
if ([fm isExecutableFileAtPath:executablePath] == NO)
|
|
{
|
|
NSRunAlertPanel(@"Debug",
|
|
@"No executable! Please build the project first.",
|
|
@"Close",nil,nil);
|
|
[debugButton setState:NSOffState];
|
|
return;
|
|
}
|
|
|
|
|
|
// Debugger
|
|
gdbPath = [[[project projectManager] prefController] stringForKey:Debugger];
|
|
if (gdbPath == nil)
|
|
{
|
|
gdbPath = @"/usr/bin/gdb";
|
|
}
|
|
|
|
if ([fm isExecutableFileAtPath:gdbPath] == NO)
|
|
{
|
|
NSRunAlertPanel(@"Debug",
|
|
@"Specified debugger `%@` cannot be executed!",
|
|
@"Close",nil,nil,gdbPath);
|
|
[debugButton setState:NSOffState];
|
|
return;
|
|
}
|
|
|
|
|
|
// Debugger
|
|
if (nil == debugger)
|
|
{
|
|
debugger = [bundleManager objectForBundleType: @"debugger"
|
|
protocol: @protocol(CodeDebugger)
|
|
fileName: [executablePath stringByDeletingLastPathComponent]];
|
|
[debugger debugExecutableAtPath: executablePath
|
|
withDebugger: gdbPath];
|
|
}
|
|
else
|
|
{
|
|
[debugger show];
|
|
}
|
|
|
|
if (nil == debugger)
|
|
NSLog(@"No debugger module found");
|
|
|
|
// turn debug button off...
|
|
// [debugButton setState:NSOffState];
|
|
}
|
|
|
|
- (void)run:(id)sender
|
|
{
|
|
NSMutableArray *args = [[NSMutableArray alloc] init];
|
|
NSPipe *logPipe;
|
|
NSPipe *errorPipe;
|
|
NSString *executablePath;
|
|
NSFileManager *fm;
|
|
|
|
executablePath = [project projectPath];
|
|
|
|
// Check if project type is executable
|
|
if ([project isExecutable])
|
|
{
|
|
NSString *prjType;
|
|
|
|
prjType = [project projectTypeName];
|
|
if ([prjType isEqualToString: @"Application"])
|
|
{
|
|
/* MyApplication.app/MyApplication */
|
|
executablePath = [executablePath stringByAppendingPathComponent:[project projectName]];
|
|
executablePath = [executablePath stringByAppendingString:@".app"];
|
|
executablePath = [executablePath stringByAppendingPathComponent:[project projectName]];
|
|
}
|
|
else if ([prjType isEqualToString: @"Tool"])
|
|
{
|
|
/* obj/MyTool */
|
|
executablePath = [executablePath stringByAppendingPathComponent:@"obj"];
|
|
executablePath = [executablePath stringByAppendingPathComponent:[project projectName]];
|
|
}
|
|
else
|
|
{
|
|
NSLog(@"Unknown project type to execute: %@", prjType);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NSRunAlertPanel(@"Run",
|
|
@"The project is not executable",
|
|
@"Close", nil, nil, nil);
|
|
[runButton setState:NSOffState];
|
|
return;
|
|
}
|
|
|
|
#ifdef __MINGW__
|
|
/* On windows we need to check the .exe file */
|
|
if ([[executablePath pathExtension] length] == 0)
|
|
{
|
|
executablePath = [executablePath stringByAppendingPathExtension: @"exe"];
|
|
}
|
|
#endif
|
|
|
|
NSLog(@"executable launch path: %@", executablePath);
|
|
|
|
/* now check if the executable does exist. Perhaps make failed */
|
|
fm = [NSFileManager defaultManager];
|
|
if (![fm isExecutableFileAtPath:executablePath])
|
|
{
|
|
NSRunAlertPanel(@"Run",
|
|
@"The project does not have an executable",
|
|
@"Close", nil, nil, nil);
|
|
[runButton setState:NSOffState];
|
|
return;
|
|
}
|
|
|
|
|
|
// [makeTask isRunning] doesn't work here.
|
|
// "waitpid 7045, result -1, error No child processes" is printed.
|
|
if (launchTask)
|
|
{
|
|
PCLogStatus(self, @"task will terminate");
|
|
[launchTask terminate];
|
|
return;
|
|
}
|
|
|
|
// Setting I/O
|
|
logPipe = [NSPipe pipe];
|
|
RELEASE(readHandle);
|
|
readHandle = [[logPipe fileHandleForReading] retain];
|
|
[stdOut setString:@""];
|
|
[readHandle waitForDataInBackgroundAndNotify];
|
|
|
|
[NOTIFICATION_CENTER addObserver:self
|
|
selector:@selector(logStdOut:)
|
|
name:NSFileHandleDataAvailableNotification
|
|
object:readHandle];
|
|
|
|
errorPipe = [NSPipe pipe];
|
|
RELEASE(errorReadHandle);
|
|
errorReadHandle = [[errorPipe fileHandleForReading] retain];
|
|
[stdOut setString:@""];
|
|
[errorReadHandle waitForDataInBackgroundAndNotify];
|
|
|
|
[NOTIFICATION_CENTER addObserver:self
|
|
selector:@selector(logErrOut:)
|
|
name:NSFileHandleDataAvailableNotification
|
|
object:errorReadHandle];
|
|
|
|
// Launch task
|
|
RELEASE(launchTask);
|
|
launchTask = [[NSTask alloc] init];
|
|
|
|
[NOTIFICATION_CENTER addObserver:self
|
|
selector:@selector(runDidTerminate:)
|
|
name:NSTaskDidTerminateNotification
|
|
object:launchTask];
|
|
[launchTask setArguments:args];
|
|
[launchTask setCurrentDirectoryPath:[project projectPath]];
|
|
[launchTask setLaunchPath:executablePath];
|
|
[launchTask setStandardOutput:logPipe];
|
|
[launchTask setStandardError:errorPipe];
|
|
[launchTask launch];
|
|
|
|
[debugButton setEnabled:NO];
|
|
|
|
_isRunning = YES;
|
|
_isErrorRunning = YES;
|
|
RELEASE(args);
|
|
}
|
|
|
|
- (void)runDidTerminate:(NSNotification *)aNotif
|
|
{
|
|
if ([aNotif object] != launchTask)
|
|
{
|
|
return;
|
|
}
|
|
|
|
[NOTIFICATION_CENTER removeObserver: self
|
|
name: NSTaskDidTerminateNotification
|
|
object: launchTask];
|
|
|
|
// Wait if there are data available.
|
|
if (_isRunning || _isErrorRunning)
|
|
{
|
|
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
|
|
beforeDate: [NSDate distantFuture]];
|
|
}
|
|
|
|
[runButton setState:NSOffState];
|
|
[debugButton setState:NSOffState];
|
|
[runButton setEnabled:YES];
|
|
[debugButton setEnabled:YES];
|
|
[componentView display];
|
|
|
|
RELEASE(launchTask);
|
|
launchTask = nil;
|
|
_isDebugging = NO;
|
|
|
|
}
|
|
|
|
- (void)logStdOut:(NSNotification *)aNotif
|
|
{
|
|
NSData *data;
|
|
|
|
if ((data = [readHandle availableData]) && [data length] > 0)
|
|
{
|
|
[self logData:data error:NO];
|
|
[readHandle waitForDataInBackgroundAndNotifyForModes:nil];
|
|
}
|
|
else
|
|
{
|
|
[NOTIFICATION_CENTER removeObserver: self
|
|
name: NSFileHandleDataAvailableNotification
|
|
object: readHandle];
|
|
|
|
_isRunning = NO;
|
|
}
|
|
}
|
|
|
|
- (void)logErrOut:(NSNotification *)aNotif
|
|
{
|
|
NSData *data;
|
|
|
|
if ((data = [errorReadHandle availableData]) && [data length] > 0)
|
|
{
|
|
[self logData:data error:YES];
|
|
[errorReadHandle waitForDataInBackgroundAndNotifyForModes:nil];
|
|
}
|
|
else
|
|
{
|
|
[NOTIFICATION_CENTER removeObserver: self
|
|
name: NSFileHandleDataAvailableNotification
|
|
object: errorReadHandle];
|
|
|
|
_isErrorRunning = NO;
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation PCProjectLauncher (BuildLogging)
|
|
|
|
- (void)logString:(NSString *)str newLine:(BOOL)newLine
|
|
{
|
|
[stdOut replaceCharactersInRange:NSMakeRange([[stdOut string] length],0)
|
|
withString:str];
|
|
|
|
if (newLine) {
|
|
[stdOut replaceCharactersInRange:NSMakeRange([[stdOut string] length], 0)
|
|
withString:@"\n"];
|
|
}
|
|
else {
|
|
[stdOut replaceCharactersInRange:NSMakeRange([[stdOut string] length], 0)
|
|
withString:@" "];
|
|
}
|
|
|
|
[stdOut scrollRangeToVisible:NSMakeRange([[stdOut string] length], 0)];
|
|
}
|
|
|
|
- (void)logData:(NSData *)data error:(BOOL)yn
|
|
{
|
|
NSString *s = nil;
|
|
NSAttributedString *as = nil;
|
|
|
|
// [self logString:s newLine:NO];
|
|
s = [[NSString alloc] initWithData:data
|
|
encoding:[NSString defaultCStringEncoding]];
|
|
as = [[NSAttributedString alloc] initWithString:s
|
|
attributes:textAttributes];
|
|
[[stdOut textStorage] appendAttributedString: as];
|
|
[stdOut scrollRangeToVisible:NSMakeRange([[stdOut string] length], 0)];
|
|
|
|
[s release];
|
|
[as release];
|
|
}
|
|
|
|
@end
|
|
|