diff --git a/AppBox.xcodeproj/project.pbxproj b/AppBox.xcodeproj/project.pbxproj index d5e94861..126f1d7f 100644 --- a/AppBox.xcodeproj/project.pbxproj +++ b/AppBox.xcodeproj/project.pbxproj @@ -41,6 +41,7 @@ DBFEEA611FA5BD1A00F09ECD /* ProvisioningProfile+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = DBFEEA591FA5BD1A00F09ECD /* ProvisioningProfile+CoreDataProperties.m */; }; DBFEEA631FA5BD1A00F09ECD /* ProvisionedDevice+CoreDataClass.m in Sources */ = {isa = PBXBuildFile; fileRef = DBFEEA5D1FA5BD1A00F09ECD /* ProvisionedDevice+CoreDataClass.m */; }; DBFEEA641FA5BD1A00F09ECD /* ProvisionedDevice+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = DBFEEA5F1FA5BD1A00F09ECD /* ProvisionedDevice+CoreDataProperties.m */; }; + E101D2BE200DE7E00062078B /* xcpretty in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D22F200DE0170062078B /* xcpretty */; }; E10B821E1DF043EA00BBE0CD /* GetSchemeScript.sh in Resources */ = {isa = PBXBuildFile; fileRef = E172E13B1DEEB3B900405EF2 /* GetSchemeScript.sh */; }; E10B82221DF0498800BBE0CD /* TeamIDScript.sh in Resources */ = {isa = PBXBuildFile; fileRef = E10B82211DF0498600BBE0CD /* TeamIDScript.sh */; }; E11633991E558635005EAB31 /* MobileProvision.m in Sources */ = {isa = PBXBuildFile; fileRef = E11633981E558635005EAB31 /* MobileProvision.m */; }; @@ -76,6 +77,24 @@ E18186DC1E8A47C10002509F /* AccountPreferencesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E18186DB1E8A47C10002509F /* AccountPreferencesViewController.m */; }; E18186DF1E8A47CD0002509F /* EmailPreferencesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E18186DE1E8A47CD0002509F /* EmailPreferencesViewController.m */; }; E18186E21E8A47D60002509F /* HelpPreferencesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E18186E11E8A47D60002509F /* HelpPreferencesViewController.m */; }; + E185337B200DF9D400E88529 /* xcpretty.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D25D200DE0170062078B /* xcpretty.rb */; }; + E185337D200DF9FA00E88529 /* ansi.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D24B200DE0170062078B /* ansi.rb */; }; + E185337E200DF9FA00E88529 /* parser.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D252200DE0170062078B /* parser.rb */; }; + E185337F200DF9FA00E88529 /* printer.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D253200DE0170062078B /* printer.rb */; }; + E1853380200DF9FA00E88529 /* snippet.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D259200DE0170062078B /* snippet.rb */; }; + E1853381200DF9FA00E88529 /* syntax.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D25A200DE0170062078B /* syntax.rb */; }; + E1853382200DF9FA00E88529 /* term.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D25B200DE0170062078B /* term.rb */; }; + E1853383200DF9FA00E88529 /* version.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D25C200DE0170062078B /* version.rb */; }; + E1853385200DFA2300E88529 /* formatter.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D24D200DE0170062078B /* formatter.rb */; }; + E1853386200DFA2300E88529 /* knock.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D24E200DE0170062078B /* knock.rb */; }; + E1853387200DFA2300E88529 /* rspec.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D24F200DE0170062078B /* rspec.rb */; }; + E1853388200DFA2300E88529 /* simple.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D250200DE0170062078B /* simple.rb */; }; + E1853389200DFA2300E88529 /* tap.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D251200DE0170062078B /* tap.rb */; }; + E185338B200DFA4100E88529 /* html.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D255200DE0170062078B /* html.rb */; }; + E185338C200DFA4100E88529 /* json_compilation_database.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D256200DE0170062078B /* json_compilation_database.rb */; }; + E185338D200DFA4100E88529 /* junit.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D257200DE0170062078B /* junit.rb */; }; + E185338E200DFA4100E88529 /* reporter.rb in CopyFiles */ = {isa = PBXBuildFile; fileRef = E101D258200DE0170062078B /* reporter.rb */; }; + E1853392200E084500E88529 /* ABLog.m in Sources */ = {isa = PBXBuildFile; fileRef = E1853391200E084500E88529 /* ABLog.m */; }; E188EA101D78485900EBCA52 /* BitlyURLShortenerService.m in Sources */ = {isa = PBXBuildFile; fileRef = E188EA0A1D78485900EBCA52 /* BitlyURLShortenerService.m */; }; E188EA111D78485900EBCA52 /* GooglURLShortenerService.m in Sources */ = {isa = PBXBuildFile; fileRef = E188EA0C1D78485900EBCA52 /* GooglURLShortenerService.m */; }; E188EA121D78485900EBCA52 /* Tiny.m in Sources */ = {isa = PBXBuildFile; fileRef = E188EA0F1D78485900EBCA52 /* Tiny.m */; }; @@ -105,14 +124,67 @@ /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ - E16438A51D746A1800CE8B7E /* Copy Files (1 item) */ = { + E101D2BC200DE72A0062078B /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; + dstPath = xcpretty/bin; + dstSubfolderSpec = 7; files = ( + E101D2BE200DE7E00062078B /* xcpretty in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E185337A200DF9AE00E88529 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = xcpretty/lib; + dstSubfolderSpec = 7; + files = ( + E185337B200DF9D400E88529 /* xcpretty.rb in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E185337C200DF9DD00E88529 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = xcpretty/lib/xcpretty; + dstSubfolderSpec = 7; + files = ( + E185337D200DF9FA00E88529 /* ansi.rb in CopyFiles */, + E185337E200DF9FA00E88529 /* parser.rb in CopyFiles */, + E185337F200DF9FA00E88529 /* printer.rb in CopyFiles */, + E1853380200DF9FA00E88529 /* snippet.rb in CopyFiles */, + E1853381200DF9FA00E88529 /* syntax.rb in CopyFiles */, + E1853382200DF9FA00E88529 /* term.rb in CopyFiles */, + E1853383200DF9FA00E88529 /* version.rb in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E1853384200DFA0900E88529 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = xcpretty/lib/xcpretty/formatters; + dstSubfolderSpec = 7; + files = ( + E1853385200DFA2300E88529 /* formatter.rb in CopyFiles */, + E1853386200DFA2300E88529 /* knock.rb in CopyFiles */, + E1853387200DFA2300E88529 /* rspec.rb in CopyFiles */, + E1853388200DFA2300E88529 /* simple.rb in CopyFiles */, + E1853389200DFA2300E88529 /* tap.rb in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + E185338A200DFA2A00E88529 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = xcpretty/lib/xcpretty/reporters; + dstSubfolderSpec = 7; + files = ( + E185338B200DFA4100E88529 /* html.rb in CopyFiles */, + E185338C200DFA4100E88529 /* json_compilation_database.rb in CopyFiles */, + E185338D200DFA4100E88529 /* junit.rb in CopyFiles */, + E185338E200DFA4100E88529 /* reporter.rb in CopyFiles */, ); - name = "Copy Files (1 item)"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ @@ -179,6 +251,24 @@ DBFEEA5D1FA5BD1A00F09ECD /* ProvisionedDevice+CoreDataClass.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ProvisionedDevice+CoreDataClass.m"; sourceTree = ""; }; DBFEEA5E1FA5BD1A00F09ECD /* ProvisionedDevice+CoreDataProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ProvisionedDevice+CoreDataProperties.h"; sourceTree = ""; }; DBFEEA5F1FA5BD1A00F09ECD /* ProvisionedDevice+CoreDataProperties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ProvisionedDevice+CoreDataProperties.m"; sourceTree = ""; }; + E101D22F200DE0170062078B /* xcpretty */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = xcpretty; sourceTree = ""; }; + E101D24B200DE0170062078B /* ansi.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = ansi.rb; sourceTree = ""; }; + E101D24D200DE0170062078B /* formatter.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = formatter.rb; sourceTree = ""; }; + E101D24E200DE0170062078B /* knock.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = knock.rb; sourceTree = ""; }; + E101D24F200DE0170062078B /* rspec.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = rspec.rb; sourceTree = ""; }; + E101D250200DE0170062078B /* simple.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = simple.rb; sourceTree = ""; }; + E101D251200DE0170062078B /* tap.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = tap.rb; sourceTree = ""; }; + E101D252200DE0170062078B /* parser.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = parser.rb; sourceTree = ""; }; + E101D253200DE0170062078B /* printer.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = printer.rb; sourceTree = ""; }; + E101D255200DE0170062078B /* html.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = html.rb; sourceTree = ""; }; + E101D256200DE0170062078B /* json_compilation_database.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = json_compilation_database.rb; sourceTree = ""; }; + E101D257200DE0170062078B /* junit.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = junit.rb; sourceTree = ""; }; + E101D258200DE0170062078B /* reporter.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = reporter.rb; sourceTree = ""; }; + E101D259200DE0170062078B /* snippet.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = snippet.rb; sourceTree = ""; }; + E101D25A200DE0170062078B /* syntax.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = syntax.rb; sourceTree = ""; }; + E101D25B200DE0170062078B /* term.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = term.rb; sourceTree = ""; }; + E101D25C200DE0170062078B /* version.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = version.rb; sourceTree = ""; }; + E101D25D200DE0170062078B /* xcpretty.rb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.ruby; path = xcpretty.rb; sourceTree = ""; }; E10B82211DF0498600BBE0CD /* TeamIDScript.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = TeamIDScript.sh; sourceTree = ""; }; E11633971E558635005EAB31 /* MobileProvision.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MobileProvision.h; sourceTree = ""; }; E11633981E558635005EAB31 /* MobileProvision.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MobileProvision.m; sourceTree = ""; }; @@ -240,6 +330,8 @@ E18186DE1E8A47CD0002509F /* EmailPreferencesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EmailPreferencesViewController.m; sourceTree = ""; }; E18186E01E8A47D60002509F /* HelpPreferencesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HelpPreferencesViewController.h; sourceTree = ""; }; E18186E11E8A47D60002509F /* HelpPreferencesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HelpPreferencesViewController.m; sourceTree = ""; }; + E1853390200E084500E88529 /* ABLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ABLog.h; sourceTree = ""; }; + E1853391200E084500E88529 /* ABLog.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ABLog.m; sourceTree = ""; }; E188EA091D78485900EBCA52 /* BitlyURLShortenerService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BitlyURLShortenerService.h; sourceTree = ""; }; E188EA0A1D78485900EBCA52 /* BitlyURLShortenerService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BitlyURLShortenerService.m; sourceTree = ""; }; E188EA0B1D78485900EBCA52 /* GooglURLShortenerService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GooglURLShortenerService.h; sourceTree = ""; }; @@ -459,6 +551,71 @@ path = EventTracker; sourceTree = ""; }; + E101D22B200DE0170062078B /* xcpretty */ = { + isa = PBXGroup; + children = ( + E101D22E200DE0170062078B /* bin */, + E101D249200DE0170062078B /* lib */, + ); + path = xcpretty; + sourceTree = ""; + }; + E101D22E200DE0170062078B /* bin */ = { + isa = PBXGroup; + children = ( + E101D22F200DE0170062078B /* xcpretty */, + ); + path = bin; + sourceTree = ""; + }; + E101D249200DE0170062078B /* lib */ = { + isa = PBXGroup; + children = ( + E101D24A200DE0170062078B /* xcpretty */, + E101D25D200DE0170062078B /* xcpretty.rb */, + ); + path = lib; + sourceTree = ""; + }; + E101D24A200DE0170062078B /* xcpretty */ = { + isa = PBXGroup; + children = ( + E101D24B200DE0170062078B /* ansi.rb */, + E101D24C200DE0170062078B /* formatters */, + E101D252200DE0170062078B /* parser.rb */, + E101D253200DE0170062078B /* printer.rb */, + E101D254200DE0170062078B /* reporters */, + E101D259200DE0170062078B /* snippet.rb */, + E101D25A200DE0170062078B /* syntax.rb */, + E101D25B200DE0170062078B /* term.rb */, + E101D25C200DE0170062078B /* version.rb */, + ); + path = xcpretty; + sourceTree = ""; + }; + E101D24C200DE0170062078B /* formatters */ = { + isa = PBXGroup; + children = ( + E101D24D200DE0170062078B /* formatter.rb */, + E101D24E200DE0170062078B /* knock.rb */, + E101D24F200DE0170062078B /* rspec.rb */, + E101D250200DE0170062078B /* simple.rb */, + E101D251200DE0170062078B /* tap.rb */, + ); + path = formatters; + sourceTree = ""; + }; + E101D254200DE0170062078B /* reporters */ = { + isa = PBXGroup; + children = ( + E101D255200DE0170062078B /* html.rb */, + E101D256200DE0170062078B /* json_compilation_database.rb */, + E101D257200DE0170062078B /* junit.rb */, + E101D258200DE0170062078B /* reporter.rb */, + ); + path = reporters; + sourceTree = ""; + }; E11633961E558609005EAB31 /* MobileProvision */ = { isa = PBXGroup; children = ( @@ -543,27 +700,6 @@ path = XCArchiveParser; sourceTree = ""; }; - E14A441A1F277567006A9102 /* XCIPAParser */ = { - isa = PBXGroup; - children = ( - ); - path = XCIPAParser; - sourceTree = ""; - }; - E14A441B1F277567006A9102 /* XCSchemeParser */ = { - isa = PBXGroup; - children = ( - ); - path = XCSchemeParser; - sourceTree = ""; - }; - E14A441C1F277567006A9102 /* XCTeamIDParser */ = { - isa = PBXGroup; - children = ( - ); - path = XCTeamIDParser; - sourceTree = ""; - }; E14F332E1E1A3D1300C3DD34 /* MenuHandler */ = { isa = PBXGroup; children = ( @@ -713,9 +849,6 @@ isa = PBXGroup; children = ( E14A44191F277567006A9102 /* XCArchiveParser */, - E14A441A1F277567006A9102 /* XCIPAParser */, - E14A441B1F277567006A9102 /* XCSchemeParser */, - E14A441C1F277567006A9102 /* XCTeamIDParser */, E16D72D21E88F99A0031FB7C /* ALOutputParser */, ); path = OutputParsers; @@ -769,6 +902,7 @@ E172E13A1DEEB34D00405EF2 /* Commands */ = { isa = PBXGroup; children = ( + E101D22B200DE0170062078B /* xcpretty */, E172E13B1DEEB3B900405EF2 /* GetSchemeScript.sh */, E10B82211DF0498600BBE0CD /* TeamIDScript.sh */, E13946141DF2FBAD00B3FAD0 /* ProjectBuildScript.sh */, @@ -822,6 +956,15 @@ path = HelpPreferencesViewController; sourceTree = ""; }; + E185338F200E082800E88529 /* LogManager */ = { + isa = PBXGroup; + children = ( + E1853390200E084500E88529 /* ABLog.h */, + E1853391200E084500E88529 /* ABLog.m */, + ); + path = LogManager; + sourceTree = ""; + }; E188EA071D78485900EBCA52 /* Tiny */ = { isa = PBXGroup; children = ( @@ -949,6 +1092,7 @@ E1CFC65B1D7E7FD9005872BE /* Common */ = { isa = PBXGroup; children = ( + E185338F200E082800E88529 /* LogManager */, E1902B501E16617B00C3E0F6 /* UserData */, E1902B491E16617B00C3E0F6 /* GitHandler */, DB2629781F95ED8900C4C647 /* Extensions */, @@ -1026,11 +1170,15 @@ C3566842BD53E8411EB6A412 /* [CP] Check Pods Manifest.lock */, E16437CB1D743A4E00CE8B7E /* Sources */, E16437CC1D743A4E00CE8B7E /* Frameworks */, - E16438A51D746A1800CE8B7E /* Copy Files (1 item) */, E16437CD1D743A4E00CE8B7E /* Resources */, 1E3F482E629E66DAF83B929E /* [CP] Embed Pods Frameworks */, 7D6EE5393594D016BF9A0389 /* [CP] Copy Pods Resources */, E1974F331E20CA770060F03F /* ShellScript */, + E101D2BC200DE72A0062078B /* CopyFiles */, + E185337A200DF9AE00E88529 /* CopyFiles */, + E185337C200DF9DD00E88529 /* CopyFiles */, + E1853384200DFA0900E88529 /* CopyFiles */, + E185338A200DFA2A00E88529 /* CopyFiles */, ); buildRules = ( ); @@ -1191,6 +1339,7 @@ E16437DD1D743A4E00CE8B7E /* AppBox.xcdatamodeld in Sources */, E16437D71D743A4E00CE8B7E /* main.m in Sources */, DB831BCB1FF6B5780065B646 /* ABHudViewController.m in Sources */, + E1853392200E084500E88529 /* ABLog.m in Sources */, E1902B481E16423200C3E0F6 /* ProjectAdvancedViewController.m in Sources */, DBF290D71FA64A590046D5CE /* ProvisioningDetailsViewController.m in Sources */, E16018F01DEC0BC200E3A377 /* DropboxViewController.m in Sources */, diff --git a/AppBox/AppBoxPrefixHeader.pch b/AppBox/AppBoxPrefixHeader.pch index 8d8a1ff0..8a18c299 100644 --- a/AppBox/AppBoxPrefixHeader.pch +++ b/AppBox/AppBoxPrefixHeader.pch @@ -19,6 +19,7 @@ #import #import "Tiny.h" +#import "ABLog.h" #import "GooglURLShortenerService.h" #import "MBProgressHUD+ProgressHud.h" diff --git a/AppBox/AppDelegate.m b/AppBox/AppDelegate.m index 4b81506b..7f18e029 100644 --- a/AppBox/AppDelegate.m +++ b/AppBox/AppDelegate.m @@ -42,20 +42,20 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { //Check for arguments NSArray *arguments = [[NSProcessInfo processInfo] arguments]; - [self addSessionLog:[NSString stringWithFormat:@"All Arguments = %@",arguments]]; + [ABLog log:@"All Command Line Arguments = %@",arguments]; for (NSString *argument in arguments) { if ([argument containsString:@"build="]) { NSArray *components = [argument componentsSeparatedByString:@"build="]; - [self addSessionLog:[NSString stringWithFormat:@"Path Components = %@",components]]; + [ABLog log:@"Path Components = %@",components]; if (components.count == 2) { [self handleProjectAtPath:[components lastObject]]; } else { - [self addSessionLog:[NSString stringWithFormat:@"Invalid command %@",arguments]]; + [ABLog log:@"Invalid command %@",arguments]; exit(abExitCodeForUnstableBuild); } break; } else if ([arguments indexOfObject:argument] == arguments.count - 1){ - [self addSessionLog:[NSString stringWithFormat:@"Normal Run"]]; + [ABLog log:@"Normal Run"]; } } @@ -80,8 +80,8 @@ +(AppDelegate *)appDelegate{ } -(void)addSessionLog:(NSString *)sessionLog{ - [_sessionLog appendFormat: @"\n\n%@ - %@",[NSDate date],sessionLog]; NSLog(@"%@",sessionLog); + [_sessionLog appendFormat: @"%@",sessionLog]; [[NSNotificationCenter defaultCenter] postNotificationName:abSessionLogUpdated object:nil]; } diff --git a/AppBox/Assets/AppBoxIcons.xcassets/CI.imageset/CI@2x.png b/AppBox/Assets/AppBoxIcons.xcassets/CI.imageset/CI@2x.png new file mode 100644 index 00000000..db174aa8 Binary files /dev/null and b/AppBox/Assets/AppBoxIcons.xcassets/CI.imageset/CI@2x.png differ diff --git a/AppBox/Assets/AppBoxIcons.xcassets/CI.imageset/CI@3x.png b/AppBox/Assets/AppBoxIcons.xcassets/CI.imageset/CI@3x.png new file mode 100644 index 00000000..4bef2175 Binary files /dev/null and b/AppBox/Assets/AppBoxIcons.xcassets/CI.imageset/CI@3x.png differ diff --git a/AppBox/Assets/AppBoxIcons.xcassets/CI.imageset/Contents.json b/AppBox/Assets/AppBoxIcons.xcassets/CI.imageset/Contents.json new file mode 100644 index 00000000..b70b94ce --- /dev/null +++ b/AppBox/Assets/AppBoxIcons.xcassets/CI.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "CI@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "CI@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AppBox/Commands/ProjectBuildScript.sh b/AppBox/Commands/ProjectBuildScript.sh index 86ee2417..7c3bd165 100755 --- a/AppBox/Commands/ProjectBuildScript.sh +++ b/AppBox/Commands/ProjectBuildScript.sh @@ -9,15 +9,20 @@ #{1} - Project Directory #Make Archove -#{2} - -workspace VisualStudioMobileCenterDemo.xcworkspace or -project -vsmcd.xcodeproj -#{3} - VisualStudioMobileCenterDemo -#{4} - /Users/emp195/Desktop/VisualStudioMobileCenterDemoGitHub/VisualStudioMobileCenterDemo/build/VSMCD.xcarchive +#{2} - Project Workspace or XcodeProject -workspace VisualStudioMobileCenterDemo.xcworkspace or -project -vsmcd.xcodeproj +#{3} - Project Scheme - VisualStudioMobileCenterDemo +#{4} - Project Archive Path - /Users/emp195/Desktop/VisualStudioMobileCenterDemoGitHub/VisualStudioMobileCenterDemo/build/VSMCD.xcarchive #Make IPA -#{5} - "/Users/emp195/Desktop/VisualStudioMobileCenterDemoGitHub/VisualStudioMobileCenterDemo/build/VSMCD.xcarchive" -#{6} - /Users/emp195/Desktop/VisualStudioMobileCenterDemoGitHub/VisualStudioMobileCenterDemo/build/ -#{7} - /Users/emp195/Desktop/VisualStudioMobileCenterDemoGitHub/VisualStudioMobileCenterDemo/exportoption.plist +#{4} - Project Archive Path - /Users/emp195/Desktop/VisualStudioMobileCenterDemoGitHub/VisualStudioMobileCenterDemo/build/VSMCD.xcarchive +#{5} - IPA Export Path - /Users/emp195/Desktop/VisualStudioMobileCenterDemoGitHub/VisualStudioMobileCenterDemo/build/ +#{6} - IPA Export options plist - /Users/emp195/Desktop/VisualStudioMobileCenterDemoGitHub/VisualStudioMobileCenterDemo/exportoption.plist + + +#Others +#{7} - Xcode Version +#{8} - xcpretty path #change directory to project @@ -36,10 +41,10 @@ then if [[ "${7}" > "9" || "${7}" == "9" ]] then echo "Building Project with Xcode 9" - xcodebuild clean -project "${2}" -scheme "${3}" archive -archivePath "${4}" -allowProvisioningUpdates -allowProvisioningDeviceRegistration + xcodebuild clean -project "${2}" -scheme "${3}" archive -archivePath "${4}" -allowProvisioningUpdates -allowProvisioningDeviceRegistration | ${8} && exit ${PIPESTATUS[0]} else echo "Building Project with Xcode 8" - xcodebuild clean -project "${2}" -scheme "${3}" archive -archivePath "${4}" + xcodebuild clean -project "${2}" -scheme "${3}" archive -archivePath "${4}" | ${8} && exit ${PIPESTATUS[0]} fi else @@ -49,10 +54,10 @@ else if [[ "${7}" > "9" || "${7}" == "9" ]] then echo "Building Project with Xcode 9" - xcodebuild clean -workspace "${2}" -scheme "${3}" archive -archivePath "${4}" + xcodebuild clean -workspace "${2}" -scheme "${3}" archive -archivePath "${4}" -allowProvisioningUpdates -allowProvisioningDeviceRegistration | ${8} && exit ${PIPESTATUS[0]} else echo "Building Project with Xcode 8" - xcodebuild clean -workspace "${2}" -scheme "${3}" archive -archivePath "${4}" + xcodebuild clean -workspace "${2}" -scheme "${3}" archive -archivePath "${4}" | ${8} && exit ${PIPESTATUS[0]} fi fi diff --git a/AppBox/Commands/xcpretty/bin/xcpretty b/AppBox/Commands/xcpretty/bin/xcpretty new file mode 100755 index 00000000..c42f1b0c --- /dev/null +++ b/AppBox/Commands/xcpretty/bin/xcpretty @@ -0,0 +1,90 @@ +#!/usr/bin/env ruby + +if RUBY_VERSION < '2.0.0' + abort "error: XCPretty requires Ruby 2.0.0 or higher." +end + +if $PROGRAM_NAME == __FILE__ + $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) +end +require 'xcpretty' +require 'optparse' + +report_options = [] +report_classes = [] +report_formats = { + "junit" => XCPretty::JUnit, + "html" => XCPretty::HTML, + "json-compilation-database" => XCPretty::JSONCompilationDatabase +} + +printer_opts = { + unicode: XCPretty::Term.unicode?, + colorize: XCPretty::Term.color?, + formatter: XCPretty::Simple +} + +OptionParser.new do |opts| + opts.banner = "Usage: xcodebuild [options] | xcpretty" + opts.on('-t', '--test', 'Use RSpec style output') do + printer_opts[:formatter] = XCPretty::RSpec + end + opts.on('-s', '--simple', 'Use simple output (default)') do + printer_opts[:formatter] = XCPretty::Simple + end + opts.on('-k', '--knock', 'Use knock output') do + printer_opts[:formatter] = XCPretty::Knock + end + opts.on('--tap', 'Use TAP output') do + printer_opts[:formatter] = XCPretty::TestAnything + end + opts.on('-f', '--formatter PATH', 'Use formatter returned from evaluating the specified Ruby file') do |path| + printer_opts[:formatter] = XCPretty.load_custom_class(path) + end + opts.on('-c', '--[no-]color', 'Use colorized output. Default is auto') do |value| + printer_opts[:colorize] = value + end + opts.on('--[no-]utf', 'Use unicode characters in output. Default is auto.') do |value| + printer_opts[:unicode] = value + end + opts.on("-r", "--report FORMAT or PATH", "Run FORMAT or PATH reporter", + " Choices: #{report_formats.keys.join(', ')}") do |format| + if report_formats.key?(format) + report_classes << report_formats[format] + else + report_classes << XCPretty.load_custom_class(format) + end + report_options << {} + end + opts.on('-o', '--output PATH', 'Write report output to PATH') do |path| + unless opts = report_options.last + XCPretty.exit_with_error('Expected report format to be specified before output path') + end + opts[:path] = path + end + opts.on('--screenshots', 'Collect screenshots in the HTML report') do + unless opts = report_options.last + XCPretty.exit_with_error('Expected screenshot argument to be specified after report format') + end + opts[:screenshots] = true + end + opts.on_tail('-h', '--help', 'Show this message') { puts opts; exit } + opts.on_tail("-v", "--version", "Show version") { puts XCPretty::VERSION; exit } + opts.parse! + + if STDIN.tty? + XCPretty.exit_with_error(opts.help) + end +end + +printer = XCPretty::Printer.new(printer_opts) +reporters = report_classes.compact.each_with_index.map { |k, i| k.new(report_options[i]) } + +STDIN.each_line do |line| + printer.pretty_print(line) + reporters.each { |r| r.handle(line) } +end + +printer.finish +reporters.each(&:finish) + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty.rb b/AppBox/Commands/xcpretty/lib/xcpretty.rb new file mode 100755 index 00000000..70b4edcd --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty.rb @@ -0,0 +1,38 @@ +require 'xcpretty/version' +require 'xcpretty/printer' +require 'xcpretty/syntax' +require 'xcpretty/snippet' +require 'xcpretty/term' +require 'xcpretty/formatters/formatter' +require 'xcpretty/formatters/simple' +require 'xcpretty/formatters/rspec' +require 'xcpretty/formatters/knock' +require 'xcpretty/formatters/tap' +require 'xcpretty/reporters/reporter' +require 'xcpretty/reporters/junit' +require 'xcpretty/reporters/html' +require 'xcpretty/reporters/json_compilation_database' + +module XCPretty + + def self.class_from_path(path) + source = File.read(path) + klass = eval(source, nil, path) + raise unless klass.is_a?(Class) + klass + end + + def self.load_custom_class(path) + $LOAD_PATH.unshift File.dirname(path) + class_from_path(path) + rescue SyntaxError => e + exit_with_error("Expected custom source file to return a class. #{e}") + end + + def self.exit_with_error(message) + $stderr.puts "[!] #{message}" + exit 1 + end + +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/ansi.rb b/AppBox/Commands/xcpretty/lib/xcpretty/ansi.rb new file mode 100755 index 00000000..477af869 --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/ansi.rb @@ -0,0 +1,72 @@ +module XCPretty + module ANSI + + attr_accessor :colorize + + FORMATTED_MATCHER = %r{\e\[(\d+)[;]?(\d+)?m(.*)\e\[0m} + + EFFECT = { + reset: '0', + bold: '1', + underline: '4' + } + + COLORS = { + black: '30', + red: '31', + green: '32', + yellow: '33', + blue: '34', + cyan: '36', + white: '37', + plain: '39' + } + + def colorize? + !!@colorize + end + + def white(text) + ansi_parse(text, :plain, :bold) + end + + def red(text) + ansi_parse(text, :red) + end + + def green(text) + ansi_parse(text, :green, :bold) + end + + def cyan(text) + ansi_parse(text, :cyan) + end + + def yellow(text) + ansi_parse(text, :yellow) + end + + def applied_effects(text) + effects = [] + if text =~ FORMATTED_MATCHER + colors = COLORS.invert[$1] + effect = EFFECT.invert[$2] + effects << colors if colors + effects << effect if effect + end + effects + end + + def strip(text) + text =~ FORMATTED_MATCHER ? $3 : text + end + + def ansi_parse(text, color, effect=nil) + return text unless colorize? + colors_code = COLORS[color] || '' + effect_code = EFFECT[effect] ? ';' + EFFECT[effect] : '' + "\e[#{colors_code}#{effect_code}m#{text}\e[#{EFFECT[:reset]}m" + end + end +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/formatters/formatter.rb b/AppBox/Commands/xcpretty/lib/xcpretty/formatters/formatter.rb new file mode 100755 index 00000000..a6fc1c65 --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/formatters/formatter.rb @@ -0,0 +1,195 @@ +# encoding: utf-8 +require 'xcpretty/ansi' +require 'xcpretty/parser' + +module XCPretty + + # Making a new formatter is easy. + # Just make a subclass of Formatter, and override any of these methods. + module FormatMethods + EMPTY = ''.freeze + + def format_analyze(file_name, file_path); EMPTY; end + def format_build_target(target, project, configuration); EMPTY; end + def format_aggregate_target(target, project, configuration); EMPTY; end + def format_analyze_target(target, project, configuration); EMPTY; end + def format_check_dependencies; EMPTY; end + def format_clean(project, target, configuration); EMPTY; end + def format_clean_target(target, project, configuration); EMPTY; end + def format_clean_remove; EMPTY; end + def format_compile(file_name, file_path); EMPTY; end + def format_compile_command(compiler_command, file_path); EMPTY; end + def format_compile_storyboard(file_name, file_path); EMPTY; end + def format_compile_xib(file_name, file_path); EMPTY; end + def format_copy_header_file(source, target); EMPTY; end + def format_copy_plist_file(source, target); EMPTY; end + def format_copy_strings_file(file_name); EMPTY; end + def format_cpresource(file); EMPTY; end + def format_generate_dsym(dsym); EMPTY; end + def format_linking(file, build_variant, arch); EMPTY; end + def format_libtool(library); EMPTY; end + def format_passing_test(suite, test, time); EMPTY; end + def format_pending_test(suite, test); EMPTY; end + def format_measuring_test(suite, test, time); EMPTY; end + def format_failing_test(suite, test, reason, file_path); EMPTY; end + def format_process_pch(file); EMPTY; end + def format_process_pch_command(file_path); EMPTY; end + def format_phase_success(phase_name); EMPTY; end + def format_phase_script_execution(script_name); EMPTY; end + def format_process_info_plist(file_name, file_path); EMPTY; end + def format_codesign(file); EMPTY; end + def format_preprocess(file); EMPTY; end + def format_pbxcp(file); EMPTY; end + def format_shell_command(command, arguments); EMPTY; end + def format_test_run_started(name); EMPTY; end + def format_test_run_finished(name, time); EMPTY; end + def format_test_suite_started(name); EMPTY; end + def format_test_summary(message, failures_per_suite); EMPTY; end + def format_touch(file_path, file_name); EMPTY; end + def format_tiffutil(file); EMPTY; end + def format_write_file(file); EMPTY; end + def format_write_auxiliary_files; EMPTY; end + + # COMPILER / LINKER ERRORS AND WARNINGS + def format_compile_error(file_name, file_path, reason, + line, cursor); EMPTY; end + def format_error(message); EMPTY; end + def format_file_missing_error(error, file_path); EMPTY; end + def format_ld_warning(message); EMPTY; end + def format_undefined_symbols(message, symbol, reference); EMPTY; end + def format_duplicate_symbols(message, file_paths); EMPTY; end + def format_warning(message); message; end + + # TODO: see how we can unify format_error and format_compile_error, + # the same for warnings + def format_compile_warning(file_name, file_path, reason, + line, cursor); EMPTY; end + end + + class Formatter + + include ANSI + include FormatMethods + + attr_reader :parser + + def initialize(use_unicode, colorize) + @use_unicode = use_unicode + @colorize = colorize + @parser = Parser.new(self) + end + + def finish + end + + # Override if you want to catch something specific with your regex + def pretty_format(text) + parser.parse(text) + end + + # If you want to print inline, override #optional_newline with '' + def optional_newline + "\n" + end + + def use_unicode? + !!@use_unicode + end + + # Will be printed by default. Override with '' if you don't want summary + def format_test_summary(executed_message, failures_per_suite) + failures = format_failures(failures_per_suite) + if failures.empty? + final_message = green(executed_message) + else + final_message = red(executed_message) + end + + text = [failures, final_message].join("\n\n\n").strip + "\n\n#{text}" + end + + ERROR = '❌ ' + ASCII_ERROR = '[x]' + + WARNING = '⚠️ ' + ASCII_WARNING = '[!]' + + def format_error(message) + "\n#{red(error_symbol + " " + message)}\n\n" + end + + def format_compile_error(file, file_path, reason, line, cursor) + "\n#{red(error_symbol + " ")}#{file_path}: #{red(reason)}\n\n" \ + "#{line}\n#{cyan(cursor)}\n\n" + end + + def format_file_missing_error(reason, file_path) + "\n#{red(error_symbol + " " + reason)} #{file_path}\n\n" + end + + def format_compile_warning(file, file_path, reason, line, cursor) + "\n#{yellow(warning_symbol + ' ')}#{file_path}: #{yellow(reason)}\n\n" \ + "#{line}\n#{cyan(cursor)}\n\n" + end + + def format_ld_warning(reason) + "#{yellow(warning_symbol + ' ' + reason)}" + end + + def format_undefined_symbols(message, symbol, reference) + "\n#{red(error_symbol + " " + message)}\n" \ + "> Symbol: #{symbol}\n" \ + "> Referenced from: #{reference}\n\n" + end + + def format_duplicate_symbols(message, file_paths) + "\n#{red(error_symbol + " " + message)}\n" \ + "> #{file_paths.map { |path| path.split('/').last }.join("\n> ")}\n" + end + + def format_will_not_be_code_signed(message) + "#{yellow(warning_symbol + " " + message)}" + end + + + private + + def format_failures(failures_per_suite) + failures_per_suite.map do |suite, failures| + formatted_failures = failures.map do |failure| + format_failure(failure) + end.join("\n\n") + + "\n#{suite}\n#{formatted_failures}" + end.join("\n") + end + + def format_failure(f) + snippet = Snippet.from_filepath(f[:file_path]) + output = " #{f[:test_case]}, #{red(f[:reason])}" + output += "\n #{cyan(f[:file_path])}" + return output if snippet.contents.empty? + + output += "\n ```\n" + if @colorize + output += Syntax.highlight(snippet) + else + output += snippet.contents + end + + output += " ```" + output + end + + def error_symbol + use_unicode? ? ERROR : ASCII_ERROR + end + + def warning_symbol + use_unicode? ? WARNING : ASCII_WARNING + end + + end +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/formatters/knock.rb b/AppBox/Commands/xcpretty/lib/xcpretty/formatters/knock.rb new file mode 100755 index 00000000..bf4c7700 --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/formatters/knock.rb @@ -0,0 +1,35 @@ +module XCPretty + + class Knock < Formatter + + FAIL = 'not ok' + PASS = 'ok' + + def format_passing_test(suite, test_case, time) + "#{PASS} - #{test_case}" + end + + def format_failing_test(test_suite, test_case, reason, file) + "#{FAIL} - #{test_case}: FAILED" + + format_failure_diagnostics(test_suite, test_case, reason, file) + end + + def format_test_summary(executed_message, failures_per_suite) + '' + end + + def format_failure_diagnostics(test_suite, test_case, reason, file) + format_diagnostics(reason) + + format_diagnostics(" #{file}: #{test_suite} - #{test_case}") + end + + private + + def format_diagnostics(text) + "\n# #{text}" + end + + end + +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/formatters/rspec.rb b/AppBox/Commands/xcpretty/lib/xcpretty/formatters/rspec.rb new file mode 100755 index 00000000..8ac10a5a --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/formatters/rspec.rb @@ -0,0 +1,33 @@ +module XCPretty + + class RSpec < Formatter + + FAIL = "F" + PASS = "." + PENDING = "P" + MEASURING = 'T' + + def optional_newline + '' + end + + def format_passing_test(suite, test_case, time) + green(PASS) + end + + def format_failing_test(test_suite, test_case, reason, file) + red(FAIL) + end + + def format_pending_test(suite, test_case) + yellow(PENDING) + end + + def format_measuring_test(suite, test_case, time) + yellow(MEASURING) + end + + end + +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/formatters/simple.rb b/AppBox/Commands/xcpretty/lib/xcpretty/formatters/simple.rb new file mode 100755 index 00000000..2cd812de --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/formatters/simple.rb @@ -0,0 +1,200 @@ +# encoding: utf-8 +require 'shellwords' + +module XCPretty + + class Simple < Formatter + + PASS = "✓" + FAIL = "✗" + PENDING = "⧖" + COMPLETION = "▸" + MEASURE = '◷' + + ASCII_PASS = "." + ASCII_FAIL = "x" + ASCII_PENDING = "P" + ASCII_COMPLETION = ">" + ASCII_MEASURE = 'T' + + INDENT = " " + + def format_analyze(file_name, file_path) + format("Analyzing", file_name) + end + + def format_build_target(target, project, configuration) + format("Building", "#{project}/#{target} [#{configuration}]") + end + + def format_aggregate_target(target, project, configuration) + format("Aggregate", "#{project}/#{target} [#{configuration}]") + end + + def format_analyze_target(target, project, configuration) + format("Analyzing", "#{project}/#{target} [#{configuration}]") + end + + def format_clean_target(target, project, configuration) + format("Cleaning", "#{project}/#{target} [#{configuration}]") + end + + def format_compile(file_name, file_path) + format("Compiling", file_name) + end + + def format_compile_xib(file_name, file_path) + format("Compiling", file_name) + end + + def format_compile_storyboard(file_name, file_path) + format("Compiling", file_name) + end + + def format_copy_header_file(source, target) + format("Copying", File.basename(source)) + end + + def format_copy_plist_file(source, target) + format("Copying", File.basename(source)) + end + + def format_copy_strings_file(file) + format("Copying", file) + end + + def format_cpresource(resource) + format("Copying", resource) + end + + def format_generate_dsym(dsym) + format("Generating '#{dsym}'") + end + + def format_libtool(library) + format("Building library", library) + end + + def format_linking(target, build_variants, arch) + format("Linking", target) + end + + def format_failing_test(suite, test_case, reason, file) + INDENT + format_test("#{test_case}, #{reason}", :fail) + end + + def format_passing_test(suite, test_case, time) + INDENT + format_test("#{test_case} (#{colored_time(time)} seconds)", + :pass) + end + + def format_pending_test(suite, test_case) + INDENT + format_test("#{test_case} [PENDING]", :pending) + end + + def format_measuring_test(suite, test_case, time) + INDENT + format_test( + "#{test_case} measured (#{colored_time(time)} seconds)", :measure + ) + end + + def format_phase_success(phase_name) + format(phase_name.capitalize, "Succeeded") + end + + def format_phase_script_execution(script_name) + format("Running script", "'#{script_name}'") + end + + def format_process_info_plist(file_name, file_path) + format("Processing", file_name) + end + + def format_process_pch(file) + format("Precompiling", file) + end + + def format_codesign(file) + format("Signing", file) + end + + def format_preprocess(file) + format("Preprocessing", file) + end + + def format_pbxcp(file) + format("Copying", file) + end + + def format_test_run_started(name) + heading("Test Suite", name, "started") + end + + def format_test_suite_started(name) + heading("", name, "") + end + + def format_touch(file_path, file_name) + format("Touching", file_name) + end + + def format_tiffutil(file_name) + format("Validating", file_name) + end + + def format_warning(message) + INDENT + yellow(message) + end + + def format_check_dependencies + format('Check Dependencies') + end + + private + + def heading(prefix, text, description) + [prefix, white(text), description].join(" ").strip + end + + def format(command, argument_text="", success=true) + symbol = status_symbol(success ? :completion : :fail) + [symbol, white(command), argument_text].join(" ").strip + end + + def format_test(test_case, status) + [status_symbol(status), test_case].join(" ").strip + end + + def status_symbol(status) + case status + when :pass + green(use_unicode? ? PASS : ASCII_PASS) + when :fail + red(use_unicode? ? FAIL : ASCII_FAIL) + when :pending + yellow(use_unicode? ? PENDING : ASCII_PENDING) + when :error + red(use_unicode? ? ERROR : ASCII_ERROR) + when :completion + yellow(use_unicode? ? COMPLETION : ASCII_COMPLETION) + when :measure + yellow(use_unicode? ? MEASURE : ASCII_MEASURE) + else + "" + end + end + + def colored_time(time) + case time.to_f + when 0..0.025 + time + when 0.026..0.100 + yellow(time) + else + red(time) + end + end + + end +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/formatters/tap.rb b/AppBox/Commands/xcpretty/lib/xcpretty/formatters/tap.rb new file mode 100755 index 00000000..dcf30e1f --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/formatters/tap.rb @@ -0,0 +1,40 @@ +module XCPretty + + class TestAnything < Knock + + attr_reader :counter + + def initialize(unicode, color) + super + @counter = 0 + end + + def format_passing_test(suite, test_case, time) + increment_counter + "#{PASS} #{counter} - #{test_case}" + end + + def format_failing_test(test_suite, test_case, reason, file) + increment_counter + "#{FAIL} #{counter} - #{test_case}" + + format_failure_diagnostics(test_suite, test_case, reason, file) + end + + def format_pending_test(test_suite, test_case) + increment_counter + "#{FAIL} #{counter} - #{test_case} # TODO Not written yet" + end + + def format_test_summary(executed_message, failures_per_suite) + counter > 0 ? "1..#{counter}" : '' + end + + private + + def increment_counter + @counter += 1 + end + end + +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/parser.rb b/AppBox/Commands/xcpretty/lib/xcpretty/parser.rb new file mode 100755 index 00000000..7dc867a5 --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/parser.rb @@ -0,0 +1,596 @@ +# encoding: utf-8 + +module XCPretty + + module Matchers + + # @regex Captured groups + # $1 file_path + # $2 file_name + ANALYZE_MATCHER = /^Analyze(?:Shallow)?\s(.*\/(.*\.(?:m|mm|cc|cpp|c|cxx)))\s*/ + + # @regex Captured groups + # $1 target + # $2 project + # $3 configuration + BUILD_TARGET_MATCHER = /^=== BUILD TARGET\s(.*)\sOF PROJECT\s(.*)\sWITH.*CONFIGURATION\s(.*)\s===/ + + # @regex Captured groups + # $1 target + # $2 project + # $3 configuration + AGGREGATE_TARGET_MATCHER = /^=== BUILD AGGREGATE TARGET\s(.*)\sOF PROJECT\s(.*)\sWITH.*CONFIGURATION\s(.*)\s===/ + + # @regex Captured groups + # $1 target + # $2 project + # $3 configuration + ANALYZE_TARGET_MATCHER = /^=== ANALYZE TARGET\s(.*)\sOF PROJECT\s(.*)\sWITH.*CONFIGURATION\s(.*)\s===/ + + # @regex Nothing returned here for now + CHECK_DEPENDENCIES_MATCHER = /^Check dependencies/ + + # @regex Captured groups + # $1 command path + # $2 arguments + SHELL_COMMAND_MATCHER = /^\s{4}(cd|setenv|(?:[\w\/:\\\s\-.]+?\/)?[\w\-]+)\s(.*)$/ + + # @regex Nothing returned here for now + CLEAN_REMOVE_MATCHER = /^Clean.Remove/ + + # @regex Captured groups + # $1 target + # $2 project + # $3 configuration + CLEAN_TARGET_MATCHER = /^=== CLEAN TARGET\s(.*)\sOF PROJECT\s(.*)\sWITH CONFIGURATION\s(.*)\s===/ + + # @regex Captured groups + # $1 = file + CODESIGN_MATCHER = /^CodeSign\s((?:\\ |[^ ])*)$/ + + # @regex Captured groups + # $1 = file + CODESIGN_FRAMEWORK_MATCHER = /^CodeSign\s((?:\\ |[^ ])*.framework)\/Versions/ + + # @regex Captured groups + # $1 file_path + # $2 file_name (e.g. KWNull.m) + COMPILE_MATCHER = /^Compile[\w]+\s.+?\s((?:\\.|[^ ])+\/((?:\\.|[^ ])+\.(?:m|mm|c|cc|cpp|cxx|swift)))\s.*/ + + # @regex Captured groups + # $1 compiler_command + # $2 file_path + COMPILE_COMMAND_MATCHER = /^\s*(.*\/bin\/clang\s.*\s\-c\s(.*\.(?:m|mm|c|cc|cpp|cxx))\s.*\.o)$/ + + # @regex Captured groups + # $1 file_path + # $2 file_name (e.g. MainMenu.xib) + COMPILE_XIB_MATCHER = /^CompileXIB\s(.*\/(.*\.xib))/ + + # @regex Captured groups + # $1 file_path + # $2 file_name (e.g. Main.storyboard) + COMPILE_STORYBOARD_MATCHER = /^CompileStoryboard\s(.*\/([^\/].*\.storyboard))/ + + # @regex Captured groups + # $1 source file + # $2 target file + COPY_HEADER_MATCHER = /^CpHeader\s(.*\.h)\s(.*\.h)/ + + # @regex Captured groups + # $1 source file + # $2 target file + COPY_PLIST_MATCHER = /^CopyPlistFile\s(.*\.plist)\s(.*\.plist)/ + + # $1 file + COPY_STRINGS_MATCHER = /^CopyStringsFile.*\/(.*.strings)/ + + # @regex Captured groups + # $1 resource + CPRESOURCE_MATCHER = /^CpResource\s(.*)\s\// + + # @regex Captured groups + # + EXECUTED_MATCHER = /^\s*Executed/ + + # @regex Captured groups + # $1 = file + # $2 = test_suite + # $3 = test_case + # $4 = reason + FAILING_TEST_MATCHER = /^\s*(.+:\d+):\serror:\s[\+\-]\[(.*)\s(.*)\]\s:(?:\s'.*'\s\[FAILED\],)?\s(.*)/ + + # @regex Captured groups + # $1 = file + # $2 = reason + UI_FAILING_TEST_MATCHER = /^\s{4}t = \s+\d+\.\d+s\s+Assertion Failure: (.*:\d+): (.*)$/ + + # @regex Captured groups + RESTARTING_TESTS_MATCHER = /^Restarting after unexpected exit or crash in.+$/ + + # @regex Captured groups + # $1 = dsym + GENERATE_DSYM_MATCHER = /^GenerateDSYMFile \/.*\/(.*\.dSYM)/ + + # @regex Captured groups + # $1 = library + LIBTOOL_MATCHER = /^Libtool.*\/(.*\.a)/ + + # @regex Captured groups + # $1 = target + # $2 = build_variants (normal, profile, debug) + # $3 = architecture + LINKING_MATCHER = /^Ld \/?.*\/(.*?) (.*) (.*)$/ + + # @regex Captured groups + # $1 = suite + # $2 = test_case + # $3 = time + TEST_CASE_PASSED_MATCHER = /^\s*Test Case\s'-\[(.*)\s(.*)\]'\spassed\s\((\d*\.\d{3})\sseconds\)/ + + + # @regex Captured groups + # $1 = suite + # $2 = test_case + TEST_CASE_STARTED_MATCHER = /^Test Case '-\[(.*) (.*)\]' started.$/ + + # @regex Captured groups + # $1 = suite + # $2 = test_case + TEST_CASE_PENDING_MATCHER = /^Test Case\s'-\[(.*)\s(.*)PENDING\]'\spassed/ + + # @regex Captured groups + # $1 = suite + # $2 = test_case + # $3 = time + TEST_CASE_MEASURED_MATCHER = /^[^:]*:[^:]*:\sTest Case\s'-\[(.*)\s(.*)\]'\smeasured\s\[Time,\sseconds\]\saverage:\s(\d*\.\d{3}),/ + + PHASE_SUCCESS_MATCHER = /^\*\*\s(.*)\sSUCCEEDED\s\*\*/ + + # @regex Captured groups + # $1 = script_name + PHASE_SCRIPT_EXECUTION_MATCHER = /^PhaseScriptExecution\s((\\\ |\S)*)\s/ + + # @regex Captured groups + # $1 = file + PROCESS_PCH_MATCHER = /^ProcessPCH\s.*\s(.*.pch)/ + + # @regex Captured groups + # $1 file_path + PROCESS_PCH_COMMAND_MATCHER = /^\s*.*\/usr\/bin\/clang\s.*\s\-c\s(.*)\s\-o\s.*/ + + # @regex Captured groups + # $1 = file + PREPROCESS_MATCHER = /^Preprocess\s(?:(?:\\ |[^ ])*)\s((?:\\ |[^ ])*)$/ + + # @regex Captured groups + # $1 = file + PBXCP_MATCHER = /^PBXCp\s((?:\\ |[^ ])*)/ + + # @regex Captured groups + # $1 = file + PROCESS_INFO_PLIST_MATCHER = /^ProcessInfoPlistFile\s.*\.plist\s(.*\/+(.*\.plist))/ + + # @regex Captured groups + # $1 = suite + # $2 = time + TESTS_RUN_COMPLETION_MATCHER = /^\s*Test Suite '(?:.*\/)?(.*[ox]ctest.*)' (finished|passed|failed) at (.*)/ + + # @regex Captured groups + # $1 = suite + # $2 = time + TEST_SUITE_STARTED_MATCHER = /^\s*Test Suite '(?:.*\/)?(.*[ox]ctest.*)' started at(.*)/ + + # @regex Captured groups + # $1 test suite name + TEST_SUITE_START_MATCHER = /^\s*Test Suite '(.*)' started at/ + + # @regex Captured groups + # $1 file_name + TIFFUTIL_MATCHER = /^TiffUtil\s(.*)/ + + # @regex Captured groups + # $1 file_path + # $2 file_name + TOUCH_MATCHER = /^Touch\s(.*\/(.+))/ + + # @regex Captured groups + # $1 file_path + WRITE_FILE_MATCHER = /^write-file\s(.*)/ + + # @regex Captured groups + WRITE_AUXILIARY_FILES = /^Write auxiliary files/ + + module Warnings + # $1 = file_path + # $2 = file_name + # $3 = reason + COMPILE_WARNING_MATCHER = /^(\/.+\/(.*):.*:.*):\swarning:\s(.*)$/ + + # $1 = ld prefix + # $2 = warning message + LD_WARNING_MATCHER = /^(ld: )warning: (.*)/ + + # @regex Captured groups + # $1 = whole warning + GENERIC_WARNING_MATCHER = /^warning:\s(.*)$/ + + # @regex Captured groups + # $1 = whole warning + WILL_NOT_BE_CODE_SIGNED_MATCHER = /^(.* will not be code signed because .*)$/ + end + + module Errors + # @regex Captured groups + # $1 = whole error + CLANG_ERROR_MATCHER = /^(clang: error:.*)$/ + + # @regex Captured groups + # $1 = whole error + CHECK_DEPENDENCIES_ERRORS_MATCHER = /^(Code\s?Sign error:.*|Code signing is required for product type .* in SDK .*|No profile matching .* found:.*|Provisioning profile .* doesn't .*|Swift is unavailable on .*|.?Use Legacy Swift Language Version.*)$/ + + # @regex Captured groups + # $1 = whole error + PROVISIONING_PROFILE_REQUIRED_MATCHER = /^(.*requires a provisioning profile.*)$/ + + # @regex Captured groups + # $1 = whole error + NO_CERTIFICATE_MATCHER = /^(No certificate matching.*)$/ + + # @regex Captured groups + # $1 = file_path + # $2 = file_name + # $3 = reason + COMPILE_ERROR_MATCHER = /^(\/.+\/(.*):.*:.*):\s(?:fatal\s)?error:\s(.*)$/ + + # @regex Captured groups + # $1 cursor (with whitespaces and tildes) + CURSOR_MATCHER = /^([\s~]*\^[\s~]*)$/ + + # @regex Captured groups + # $1 = whole error. + # it varies a lot, not sure if it makes sense to catch everything separately + FATAL_ERROR_MATCHER = /^(fatal error:.*)$/ + + # @regex Captured groups + # $1 = whole error. + # $2 = file path + FILE_MISSING_ERROR_MATCHER = /^:0:\s(error:\s.*)\s'(\/.+\/.*\..*)'$/ + + # @regex Captured groups + # $1 = whole error + LD_ERROR_MATCHER = /^(ld:.*)/ + + # @regex Captured groups + # $1 file path + LINKER_DUPLICATE_SYMBOLS_LOCATION_MATCHER = /^\s+(\/.*\.o[\)]?)$/ + + # @regex Captured groups + # $1 reason + LINKER_DUPLICATE_SYMBOLS_MATCHER = /^(duplicate symbol .*):$/ + + # @regex Captured groups + # $1 symbol location + LINKER_UNDEFINED_SYMBOL_LOCATION_MATCHER = /^(.* in .*\.o)$/ + + # @regex Captured groups + # $1 reason + LINKER_UNDEFINED_SYMBOLS_MATCHER = /^(Undefined symbols for architecture .*):$/ + + # @regex Captured groups + # $1 reason + PODS_ERROR_MATCHER = /^(error:\s.*)/ + + # @regex Captured groups + # $1 = reference + SYMBOL_REFERENCED_FROM_MATCHER = /\s+"(.*)", referenced from:$/ + + # @regex Captured groups + # $1 = error reason + MODULE_INCLUDES_ERROR_MATCHER = /^\:.*?:.*?:\s(?:fatal\s)?(error:\s.*)$/ + end + end + + class Parser + + include Matchers + include Matchers::Errors + include Matchers::Warnings + + attr_reader :formatter + + def initialize(formatter) + @formatter = formatter + end + + def parse(text) + update_test_state(text) + update_error_state(text) + update_linker_failure_state(text) + + return format_compile_error if should_format_error? + return format_compile_warning if should_format_warning? + return format_undefined_symbols if should_format_undefined_symbols? + return format_duplicate_symbols if should_format_duplicate_symbols? + + case text + when ANALYZE_MATCHER + formatter.format_analyze($2, $1) + when BUILD_TARGET_MATCHER + formatter.format_build_target($1, $2, $3) + when AGGREGATE_TARGET_MATCHER + formatter.format_aggregate_target($1, $2, $3) + when ANALYZE_TARGET_MATCHER + formatter.format_analyze_target($1, $2, $3) + when CLEAN_REMOVE_MATCHER + formatter.format_clean_remove + when CLEAN_TARGET_MATCHER + formatter.format_clean_target($1, $2, $3) + when COPY_STRINGS_MATCHER + formatter.format_copy_strings_file($1) + when CHECK_DEPENDENCIES_MATCHER + formatter.format_check_dependencies + when CLANG_ERROR_MATCHER + formatter.format_error($1) + when CODESIGN_FRAMEWORK_MATCHER + formatter.format_codesign($1) + when CODESIGN_MATCHER + formatter.format_codesign($1) + when CHECK_DEPENDENCIES_ERRORS_MATCHER + formatter.format_error($1) + when PROVISIONING_PROFILE_REQUIRED_MATCHER + formatter.format_error($1) + when NO_CERTIFICATE_MATCHER + formatter.format_error($1) + when COMPILE_MATCHER + formatter.format_compile($2, $1) + when COMPILE_COMMAND_MATCHER + formatter.format_compile_command($1, $2) + when COMPILE_XIB_MATCHER + formatter.format_compile_xib($2, $1) + when COMPILE_STORYBOARD_MATCHER + formatter.format_compile_storyboard($2, $1) + when COPY_HEADER_MATCHER + formatter.format_copy_header_file($1, $2) + when COPY_PLIST_MATCHER + formatter.format_copy_plist_file($1, $2) + when CPRESOURCE_MATCHER + formatter.format_cpresource($1) + when EXECUTED_MATCHER + format_summary_if_needed(text) + when RESTARTING_TESTS_MATCHER + formatter.format_failing_test(@test_suite, @test_case, "Test crashed", "n/a") + when UI_FAILING_TEST_MATCHER + formatter.format_failing_test(@test_suite, @test_case, $2, $1) + when FAILING_TEST_MATCHER + formatter.format_failing_test($2, $3, $4, $1) + when FATAL_ERROR_MATCHER + formatter.format_error($1) + when FILE_MISSING_ERROR_MATCHER + formatter.format_file_missing_error($1, $2) + when GENERATE_DSYM_MATCHER + formatter.format_generate_dsym($1) + when LD_WARNING_MATCHER + formatter.format_ld_warning($1 + $2) + when LD_ERROR_MATCHER + formatter.format_error($1) + when LIBTOOL_MATCHER + formatter.format_libtool($1) + when LINKING_MATCHER + formatter.format_linking($1, $2, $3) + when MODULE_INCLUDES_ERROR_MATCHER + formatter.format_error($1) + when TEST_CASE_MEASURED_MATCHER + formatter.format_measuring_test($1, $2, $3) + when TEST_CASE_PENDING_MATCHER + formatter.format_pending_test($1, $2) + when TEST_CASE_PASSED_MATCHER + formatter.format_passing_test($1, $2, $3) + when PODS_ERROR_MATCHER + formatter.format_error($1) + when PROCESS_INFO_PLIST_MATCHER + formatter.format_process_info_plist(*unescaped($2, $1)) + when PHASE_SCRIPT_EXECUTION_MATCHER + formatter.format_phase_script_execution(*unescaped($1)) + when PHASE_SUCCESS_MATCHER + formatter.format_phase_success($1) + when PROCESS_PCH_MATCHER + formatter.format_process_pch($1) + when PROCESS_PCH_COMMAND_MATCHER + formatter.format_process_pch_command($1) + when PREPROCESS_MATCHER + formatter.format_preprocess($1) + when PBXCP_MATCHER + formatter.format_pbxcp($1) + when TESTS_RUN_COMPLETION_MATCHER + formatter.format_test_run_finished($1, $3) + when TEST_SUITE_STARTED_MATCHER + formatter.format_test_run_started($1) + when TEST_SUITE_START_MATCHER + formatter.format_test_suite_started($1) + when TIFFUTIL_MATCHER + formatter.format_tiffutil($1) + when TOUCH_MATCHER + formatter.format_touch($1, $2) + when WRITE_FILE_MATCHER + formatter.format_write_file($1) + when WRITE_AUXILIARY_FILES + formatter.format_write_auxiliary_files + when SHELL_COMMAND_MATCHER + formatter.format_shell_command($1, $2) + when GENERIC_WARNING_MATCHER + formatter.format_warning($1) + when WILL_NOT_BE_CODE_SIGNED_MATCHER + formatter.format_will_not_be_code_signed($1) + else + "" + end + end + + private + + def update_test_state(text) + case text + when TEST_SUITE_STARTED_MATCHER + @tests_done = false + @formatted_summary = false + @failures = {} + when TEST_CASE_STARTED_MATCHER + @test_suite = $1 + @test_case = $2 + when TESTS_RUN_COMPLETION_MATCHER + @tests_done = true + when FAILING_TEST_MATCHER + store_failure(file: $1, test_suite: $2, test_case: $3, reason: $4) + when UI_FAILING_TEST_MATCHER + store_failure(file: $1, test_suite: @test_suite, test_case: @test_case, reason: $2) + when RESTARTING_TESTS_MATCHER + store_failure(file: "n/a", test_suite: @test_suite, test_case: @test_case, reason: "Test crashed") + end + end + + # @ return Hash { :file_name, :file_path, :reason, :line } + def update_error_state(text) + update_error = lambda { + current_issue[:reason] = $3 + current_issue[:file_path] = $1 + current_issue[:file_name] = $2 + } + if text =~ COMPILE_ERROR_MATCHER + @formatting_error = true + update_error.call + elsif text =~ COMPILE_WARNING_MATCHER + @formatting_warning = true + update_error.call + elsif text =~ CURSOR_MATCHER + current_issue[:cursor] = $1.chomp + elsif @formatting_error || @formatting_warning + current_issue[:line] = text.chomp + end + end + + def update_linker_failure_state(text) + if text =~ LINKER_UNDEFINED_SYMBOLS_MATCHER || + text =~ LINKER_DUPLICATE_SYMBOLS_MATCHER + + current_linker_failure[:message] = $1 + @formatting_linker_failure = true + end + return unless @formatting_linker_failure + + case text + when SYMBOL_REFERENCED_FROM_MATCHER + current_linker_failure[:symbol] = $1 + when LINKER_UNDEFINED_SYMBOL_LOCATION_MATCHER + current_linker_failure[:reference] = text.strip + when LINKER_DUPLICATE_SYMBOLS_LOCATION_MATCHER + current_linker_failure[:files] << $1 + end + end + + # TODO: clean up the mess around all this + def should_format_error? + @formatting_error && error_or_warning_is_present + end + + def should_format_warning? + @formatting_warning && error_or_warning_is_present + end + + def error_or_warning_is_present + current_issue[:reason] && current_issue[:cursor] && current_issue[:line] + end + + def should_format_undefined_symbols? + current_linker_failure[:message] && current_linker_failure[:symbol] && current_linker_failure[:reference] + end + + def should_format_duplicate_symbols? + current_linker_failure[:message] && current_linker_failure[:files].count > 1 + end + + def current_issue + @current_issue ||= {} + end + + def current_linker_failure + @linker_failure ||= {files: []} + end + + def format_compile_error + error = current_issue.dup + @current_issue = {} + @formatting_error = false + formatter.format_compile_error(error[:file_name], + error[:file_path], + error[:reason], + error[:line], + error[:cursor]) + end + + def format_compile_warning + warning = current_issue.dup + @current_issue = {} + @formatting_warning = false + formatter.format_compile_warning(warning[:file_name], + warning[:file_path], + warning[:reason], + warning[:line], + warning[:cursor]) + end + + def format_undefined_symbols + result = formatter.format_undefined_symbols( + current_linker_failure[:message], + current_linker_failure[:symbol], + current_linker_failure[:reference] + ) + reset_linker_format_state + result + end + + def format_duplicate_symbols + result = formatter.format_duplicate_symbols( + current_linker_failure[:message], + current_linker_failure[:files] + ) + reset_linker_format_state + result + end + + def reset_linker_format_state + @linker_failure = nil + @formatting_linker_failure = false + end + + def store_failure(file: nil, test_suite: nil, test_case: nil, reason: nil) + failures_per_suite[test_suite] ||= [] + failures_per_suite[test_suite] << { + file_path: file, + reason: reason, + test_case: test_case + } + end + + def failures_per_suite + @failures ||= {} + end + + def format_summary_if_needed(executed_message) + return "" unless should_format_summary? + + @formatted_summary = true + formatter.format_test_summary(executed_message, failures_per_suite) + end + + def should_format_summary? + @tests_done && !@formatted_summary + end + + def unescaped(*escaped_values) + escaped_values.map { |v| v.delete('\\') } + end + + end +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/printer.rb b/AppBox/Commands/xcpretty/lib/xcpretty/printer.rb new file mode 100755 index 00000000..e0c7d7b0 --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/printer.rb @@ -0,0 +1,28 @@ +require "xcpretty/ansi" + +module XCPretty + + class Printer + + attr_reader :formatter + + def initialize(params) + klass = params[:formatter] + @formatter = klass.new(params[:unicode], params[:colorize]) + end + + def finish + @formatter.finish + end + + def pretty_print(text) + formatted_text = formatter.pretty_format(text) + unless formatted_text.empty? + STDOUT.print(formatted_text + formatter.optional_newline) + STDOUT.flush + end + end + + end +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/reporters/html.rb b/AppBox/Commands/xcpretty/lib/xcpretty/reporters/html.rb new file mode 100755 index 00000000..5e304665 --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/reporters/html.rb @@ -0,0 +1,93 @@ +module XCPretty + class HTML < Reporter + + FILEPATH = 'build/reports/tests.html' + TEMPLATE = File.expand_path('../../../../assets/report.html.erb', __FILE__) + SCREENSHOT_DIR = 'build/reports' + + def load_dependencies + unless @@loaded ||= false + require 'fileutils' + require 'pathname' + require 'erb' + @@loaded = true + end + end + + def initialize(options) + super(options) + @test_suites = {} + @collect_screenshots = options[:screenshots] + end + + def handle(line) + @parser.parse(line) + end + + def format_failing_test(suite, test_case, reason, file) + add_test(suite, name: test_case, failing: true, + reason: reason, file: file, + snippet: formatted_snippet(file), + screenshots: []) + end + + def format_passing_test(suite, test_case, time) + add_test(suite, name: test_case, time: time, screenshots: []) + end + + private + + def formatted_snippet(filepath) + snippet = Snippet.from_filepath(filepath) + Syntax.highlight_html(snippet) + end + + def add_test(suite_name, data) + @test_count += 1 + @test_suites[suite_name] ||= {tests: []} + @test_suites[suite_name][:tests] << data + if data[:failing] + @test_suites[suite_name][:failing] = true + @fail_count += 1 + end + end + + def write_report + if @collect_screenshots + load_screenshots + end + File.open(@filepath, 'w') do |f| + # WAT: get rid of these locals. BTW Cucumber fails if you remove them + test_suites = @test_suites + fail_count = @fail_count + test_count = @test_count + erb = ERB.new(File.open(TEMPLATE, 'r').read) + f.write erb.result(binding) + end + end + + def load_screenshots + Dir.foreach(SCREENSHOT_DIR) do |item| + next if item == '.' || item == '..' || File.extname(item) != '.png' + + test = find_test(item) + next if test.nil? + + test[:screenshots] << item + end + end + + def find_test(image_name) + @test_suites.each do |name, info| + info[:tests].each do |test, index| + combined_name = name + '_' + test[:name] + test_name_matches = image_name.start_with?(test[:name]) + combined_name_matches = image_name.start_with?(combined_name) + return test if test_name_matches || combined_name_matches + end + end + nil + end + end +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/reporters/json_compilation_database.rb b/AppBox/Commands/xcpretty/lib/xcpretty/reporters/json_compilation_database.rb new file mode 100755 index 00000000..60527971 --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/reporters/json_compilation_database.rb @@ -0,0 +1,51 @@ +module XCPretty + class JSONCompilationDatabase < Reporter + + FILEPATH = 'build/reports/compilation_db.json' + + def load_dependencies + unless @@loaded ||= false + require 'fileutils' + require 'pathname' + require 'json' + @@loaded = true + end + end + + def initialize(options) + super(options) + @compilation_units = [] + @pch_path = nil + @current_file = nil + @current_path = nil + end + + def format_process_pch_command(file_path) + @pch_path = file_path + end + + def format_compile(file_name, file_path) + @current_file = file_name + @current_path = file_path + end + + def format_compile_command(compiler_command, file_path) + directory = file_path.gsub("#{@current_path}", '').gsub(/\/$/, '') + directory = '/' if directory.empty? + + cmd = compiler_command + cmd = cmd.gsub(/(\-include)\s.*\.pch/, "\\1 #{@pch_path}") if @pch_path + + @compilation_units << {command: cmd, + file: @current_path, + directory: directory} + end + + def write_report + File.open(@filepath, 'w') do |f| + f.write(@compilation_units.to_json) + end + end + end +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/reporters/junit.rb b/AppBox/Commands/xcpretty/lib/xcpretty/reporters/junit.rb new file mode 100755 index 00000000..56302fda --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/reporters/junit.rb @@ -0,0 +1,102 @@ +module XCPretty + class JUnit < Reporter + + FILEPATH = 'build/reports/junit.xml' + + def load_dependencies + unless @@loaded ||= false + require 'fileutils' + require 'pathname' + require 'rexml/document' + require 'rexml/formatters/pretty' + @@loaded = true + end + end + + def initialize(options) + super(options) + @directory = `pwd`.strip + @document = REXML::Document.new + @document << REXML::XMLDecl.new('1.0', 'UTF-8') + @document.add_element('testsuites') + @total_fails = 0 + @total_tests = 0 + end + + def handle(line) + @parser.parse(line) + end + + def format_test_run_started(name) + @document.root.add_attribute('name', name) + end + + def format_passing_test(classname, test_case, time) + test_node = suite(classname).add_element('testcase') + test_node.attributes['classname'] = classname + test_node.attributes['name'] = test_case + test_node.attributes['time'] = time + @test_count += 1 + end + + def format_pending_test(classname, test_case) + test_node = suite(classname).add_element('testcase') + test_node.attributes['classname'] = classname + test_node.attributes['name'] = test_case + test_node.add_element('skipped') + @test_count += 1 + end + + def format_failing_test(classname, test_case, reason, file) + test_node = suite(classname).add_element('testcase') + test_node.attributes['classname'] = classname + test_node.attributes['name'] = test_case + fail_node = test_node.add_element('failure') + fail_node.attributes['message'] = reason + fail_node.text = file.sub(@directory + '/', '') + @test_count += 1 + @fail_count += 1 + end + + def finish + set_test_counters + @document.root.attributes['tests'] = @total_tests + @document.root.attributes['failures'] = @total_fails + super + end + + def write_report + formatter = REXML::Formatters::Pretty.new(2) + formatter.compact = true + output_file = File.open(@filepath, 'w+') + result = formatter.write(@document, output_file) + output_file.close + result + end + + private + + def suite(classname) + if @last_suite && @last_suite.attributes['name'] == classname + return @last_suite + end + + set_test_counters + @last_suite = @document.root.add_element('testsuite') + @last_suite.attributes['name'] = classname + @last_suite + end + + def set_test_counters + if @last_suite + @last_suite.attributes['tests'] = @test_count + @last_suite.attributes['failures'] = @fail_count + end + @total_fails += @fail_count || 0 + @total_tests += @test_count || 0 + @test_count = 0 + @fail_count = 0 + end + end +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/reporters/reporter.rb b/AppBox/Commands/xcpretty/lib/xcpretty/reporters/reporter.rb new file mode 100755 index 00000000..e03d8cdc --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/reporters/reporter.rb @@ -0,0 +1,62 @@ +module XCPretty + + class Reporter < Formatter + FILEPATH = 'build/reports/tests.txt' + + attr_reader :tests + + def load_dependencies + unless @@loaded ||= false + require 'fileutils' + @@loaded = true + end + end + + def initialize(options) + super(true, true) + load_dependencies + @filepath = options[:path] || self.class::FILEPATH + @test_count = 0 + @fail_count = 0 + @tests = [] + end + + def handle(line) + @parser.parse(line) + end + + def finish + FileUtils.mkdir_p(File.dirname(@filepath)) + write_report + end + + def format_failing_test(suite, test_case, reason, file) + @test_count += 1 + @fail_count += 1 + @tests.push("#{test_case} in #{file} FAILED: #{reason}") + end + + def format_passing_test(suite, test_case, time) + @test_count += 1 + @tests.push("#{test_case} PASSED") + end + + def format_pending_test(classname, test_case) + @test_count += 1 + @tests.push("#{test_case} IS PENDING") + end + + def write_report + File.open(@filepath, 'w') do |f| + # WAT: get rid of these locals. BTW Cucumber fails if you remove them + output_string = @tests.join("\n") + output_string += + "\nFINISHED RUNNING #{@test_count} TESTS WITH #{@fail_count} FAILURES" + f.write output_string + end + end + + end + +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/snippet.rb b/AppBox/Commands/xcpretty/lib/xcpretty/snippet.rb new file mode 100755 index 00000000..7b8e3d78 --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/snippet.rb @@ -0,0 +1,38 @@ +module XCPretty + class Snippet + attr_reader :contents, :file_path + + def initialize(contents = '', file_path = '') + @contents = contents + @file_path = file_path + end + + def self.from_filepath(filepath) + path, line = filepath.split(':') + file = File.open(path) + + text = read_snippet(file, line) + + file.close + new(text, filepath) + rescue + new('', filepath) + end + + + def self.read_snippet(file, around_line) + text = '' + starting_position = around_line.to_i - 2 + starting_position.times { file.gets } + 3.times { text += readline(file) } + text + end + + def self.readline(file) + file.gets + $_ || '' + end + + end +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/syntax.rb b/AppBox/Commands/xcpretty/lib/xcpretty/syntax.rb new file mode 100755 index 00000000..6778d1d5 --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/syntax.rb @@ -0,0 +1,58 @@ +begin + require 'rouge' +rescue LoadError + # rubocop:disable Style/ConstantName + Rouge = nil + # rubocop:enable Style/ConstantName +end + +require 'xcpretty/snippet' + +module XCPretty + module Syntax + def self.highlight(snippet) + return snippet.contents unless Rouge + highlight_with_formatter(snippet, Rouge::Formatters::Terminal256.new) + end + + def self.highlight_html(snippet) + return snippet.contents unless Rouge + highlight_with_formatter(snippet, Rouge::Formatters::HTML.new) + end + + def self.highlight_with_formatter(snippet, formatter) + if snippet.file_path.include?(':') + filename = snippet.file_path.rpartition(':').first + else + filename = snippet.file_path + end + + lexer = find_lexer(filename, snippet.contents) + if lexer + formatter.format(lexer.lex(snippet.contents)) + else + snippet.contents + end + end + + # @param [String] filename The filename + # @param [String] contents The contents of the file + # @return [Rouge::Lexer] + def self.find_lexer(filename, contents) + case File.extname(filename) + when '.cpp', '.cc', '.c++', '.cxx', '.hpp', '.h++', '.hxx' + Rouge::Lexers::Cpp + when '.m', '.h' then Rouge::Lexers::ObjectiveC + when '.swift' then Rouge::Lexers::Swift + when '.ruby', '.rb' then Rouge::Lexers::Ruby + else + options = { + filename: File.basename(filename), + source: contents + } + Rouge::Lexer.guesses(options).first + end + end + end +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/term.rb b/AppBox/Commands/xcpretty/lib/xcpretty/term.rb new file mode 100755 index 00000000..b24c3381 --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/term.rb @@ -0,0 +1,14 @@ + +module XCPretty + module Term + + def self.unicode? + Encoding.default_external == Encoding::UTF_8 + end + + def self.color? + STDOUT.tty? + end + end +end + diff --git a/AppBox/Commands/xcpretty/lib/xcpretty/version.rb b/AppBox/Commands/xcpretty/lib/xcpretty/version.rb new file mode 100755 index 00000000..2f28e3d6 --- /dev/null +++ b/AppBox/Commands/xcpretty/lib/xcpretty/version.rb @@ -0,0 +1,4 @@ +module XCPretty + VERSION = "0.2.8" +end + diff --git a/AppBox/Common/KeychainHandler/KeychainHandler.m b/AppBox/Common/KeychainHandler/KeychainHandler.m index 47dc9efa..0c90e889 100644 --- a/AppBox/Common/KeychainHandler/KeychainHandler.m +++ b/AppBox/Common/KeychainHandler/KeychainHandler.m @@ -37,7 +37,7 @@ + (NSArray *)getAllTeamId{ //Check existing team id NSPredicate *existingTeam = [NSPredicate predicateWithFormat:@"SELF.teamId = %@ AND SELF.teamName = %@ AND SELF.fullName = %@",teamId, teamName, fullName]; - NSLog(@"filter - %@",[plainCertifcates filteredArrayUsingPredicate:existingTeam]); + [ABLog log:@"filter - %@",[plainCertifcates filteredArrayUsingPredicate:existingTeam]]; //Filter invalid cert if ([plainCertifcates filteredArrayUsingPredicate:existingTeam].count == 0){ diff --git a/AppBox/Common/LogManager/ABLog.h b/AppBox/Common/LogManager/ABLog.h new file mode 100644 index 00000000..ff47ea84 --- /dev/null +++ b/AppBox/Common/LogManager/ABLog.h @@ -0,0 +1,15 @@ +// +// ABLog.h +// AppBox +// +// Created by Vineet Choudhary on 16/01/18. +// Copyright © 2018 Developer Insider. All rights reserved. +// + +#import + +@interface ABLog : NSObject + ++(void)log:(NSString *)format, ...; + +@end diff --git a/AppBox/Common/LogManager/ABLog.m b/AppBox/Common/LogManager/ABLog.m new file mode 100644 index 00000000..cdddcea8 --- /dev/null +++ b/AppBox/Common/LogManager/ABLog.m @@ -0,0 +1,22 @@ +// +// ABLog.m +// AppBox +// +// Created by Vineet Choudhary on 16/01/18. +// Copyright © 2018 Developer Insider. All rights reserved. +// + +#import "ABLog.h" + +@implementation ABLog + ++(void)log:(NSString *)format, ...{ + if ([UserData debugLog]) { + va_list args; + va_start(args, format); + NSLogv(format, args); + va_end(args); + } +} + +@end diff --git a/AppBox/Common/MailHandler/MailHandler.m b/AppBox/Common/MailHandler/MailHandler.m index dc7c8ec4..729f65c8 100644 --- a/AppBox/Common/MailHandler/MailHandler.m +++ b/AppBox/Common/MailHandler/MailHandler.m @@ -69,7 +69,7 @@ + (void)sendMailForProject:(XCProject *)project complition:(void (^) (BOOL succe [parameters setValue:subject forKey:@"subject"]; [parameters setValue:body forKey:@"html"]; - [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"Mail Parameters - %@", parameters]]; + [ABLog log:@"Mail Parameters - %@", parameters]; AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; [manager setRequestSerializer: [AFHTTPRequestSerializer serializer]]; diff --git a/AppBox/Common/NetworkHandler/NetworkHandler.m b/AppBox/Common/NetworkHandler/NetworkHandler.m index 5f0f3ef2..4288a04e 100644 --- a/AppBox/Common/NetworkHandler/NetworkHandler.m +++ b/AppBox/Common/NetworkHandler/NetworkHandler.m @@ -17,7 +17,7 @@ +(void)requestWithURL:(NSString *)url withParameters:(id)parmeters andRequestTyp [manager setResponseSerializer: [AFJSONResponseSerializer serializer]]; if (requestType == RequestGET){ [manager GET:url parameters:parmeters progress:^(NSProgress * _Nonnull downloadProgress) { - [[AppDelegate appDelegate] addSessionLog:@"Request In Progress!!"]; + [ABLog log:@"Request In Progress - %@", url]; } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { completion(responseObject, nil); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { @@ -29,7 +29,7 @@ +(void)requestWithURL:(NSString *)url withParameters:(id)parmeters andRequestTyp }]; }else if (requestType == RequestPOST){ [manager POST:url parameters:parmeters progress:^(NSProgress * _Nonnull uploadProgress) { - [[AppDelegate appDelegate] addSessionLog:@"Request In Progress!!"]; + [ABLog log:@"Request In Progress - %@", url]; } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { completion(responseObject, nil); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { @@ -49,7 +49,7 @@ +(void)getContentOfURL:(NSString *)url withParameters:(id)parmeters withRequestT [manager.responseSerializer setAcceptableContentTypes:[NSSet setWithObjects:@"application/javascript", @"text/html", @"text/javascript", nil]]; if (requestType == RequestGET){ [manager GET:url parameters:parmeters progress:^(NSProgress * _Nonnull downloadProgress) { - [[AppDelegate appDelegate] addSessionLog:@"Request In Progress!!"]; + [ABLog log:@"Request In Progress - %@", url]; } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { completion(responseObject, ((NSHTTPURLResponse *)task.response).statusCode ,nil); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { @@ -57,7 +57,7 @@ +(void)getContentOfURL:(NSString *)url withParameters:(id)parmeters withRequestT }]; }else if (requestType == RequestPOST){ [manager POST:url parameters:parmeters progress:^(NSProgress * _Nonnull uploadProgress) { - [[AppDelegate appDelegate] addSessionLog:@"Request In Progress!!"]; + [ABLog log:@"Request In Progress - %@", url]; } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { completion(responseObject, ((NSHTTPURLResponse *)task.response).statusCode ,nil); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { diff --git a/AppBox/Common/OutputParsers/XCArchiveParser/XCArchiveParser.m b/AppBox/Common/OutputParsers/XCArchiveParser/XCArchiveParser.m index 4c284ad8..ff3b39c0 100644 --- a/AppBox/Common/OutputParsers/XCArchiveParser/XCArchiveParser.m +++ b/AppBox/Common/OutputParsers/XCArchiveParser/XCArchiveParser.m @@ -35,13 +35,67 @@ +(XCArchiveResult *)archiveResultMessageFromString:(NSString *)unformatedMessage xcArchiveResult.type = XCArchiveResultExportFailed; [xcArchiveResult.message appendString:@"Export Failed"]; } - else if ([unformatedMessage containsString:@"check dependencies"]){ - xcArchiveResult.type = XCArchiveResultTypeCheckDependencies; - [xcArchiveResult.message appendString:@"Check Dependencies"]; + else{ + xcArchiveResult.type = XCArchiveResultTypeCheckPrint; + if ([unformatedMessage containsString:@"check dependencies"]){ + [xcArchiveResult.message appendString:@"Check Dependencies"]; + } + else if ([unformatedMessage containsString:@"clean.remove"]) { + [self appendLastPathComponentIn:@"Clean" from:xcArchiveResult]; + } + else if ([unformatedMessage containsString:@"compilec"]) { + [self appendLastPathComponentIn:@"CompileC" from:xcArchiveResult]; + } + else if ([unformatedMessage containsString:@"compilestoryboard"]) { + [self appendLastPathComponentIn:@"Compile Storyboard" from:xcArchiveResult]; + } + else if ([unformatedMessage containsString:@"compileassetcatalog"]) { + [self appendLastPathComponentIn:@"Compile Asset Catalog" from:xcArchiveResult]; + } + else if ([unformatedMessage containsString:@"compilestoryboard"]) { + [self appendLastPathComponentIn:@"Compile Storyboard" from:xcArchiveResult]; + } + else if ([unformatedMessage containsString:@"processinfoplistfile"]) { + [self appendLastPathComponentIn:@"Process info.plist File" from:xcArchiveResult]; + } + else if ([unformatedMessage containsString:@"generatedsymfile"]) { + [self appendLastPathComponentIn:@"Generate DSYM File" from:xcArchiveResult]; + } + else if ([unformatedMessage containsString:@"linkstoryboards"]) { + [xcArchiveResult.message appendString:@"Linking Storyboards"]; + } + else if ([unformatedMessage containsString:@"processproductpackaging"]) { + [xcArchiveResult.message appendString:@"Process Product Packaging"]; + } + else if ([unformatedMessage containsString:@"touch"]) { + [self appendLastPathComponentIn:@"Touch" from:xcArchiveResult]; + } + else if ([unformatedMessage containsString:@"codesign"]) { + [unformatedMessage enumerateLinesUsingBlock:^(NSString * _Nonnull line, BOOL * _Nonnull stop) { + if ([line containsString:@"signing identity"] || [line containsString:@"provisioning profile"]){ + [xcArchiveResult.message appendString:line]; + } + }]; + } + else if ([unformatedMessage containsString:@"validate"]) { + [xcArchiveResult.message appendString:@"Validating"]; + } + else if ([unformatedMessage containsString:@"building project..."]) { + [xcArchiveResult.message appendString:xcArchiveResult.completeMessage]; + } } - return xcArchiveResult; } ++(void)appendLastPathComponentIn:(NSString *)string from:(XCArchiveResult *)result{ + NSString *firstLine = [[result.completeMessage componentsSeparatedByString:@"\n"] firstObject]; + if (firstLine){ + NSString *lastObject = [[firstLine componentsSeparatedByString:@"/"] lastObject]; + if (lastObject){ + [result.message appendFormat:@"%@ : %@",string, lastObject.capitalizedString]; + } + } +} + @end diff --git a/AppBox/Common/OutputParsers/XCArchiveParser/XCArchiveResult.h b/AppBox/Common/OutputParsers/XCArchiveParser/XCArchiveResult.h index e57c254c..da460e33 100644 --- a/AppBox/Common/OutputParsers/XCArchiveParser/XCArchiveResult.h +++ b/AppBox/Common/OutputParsers/XCArchiveParser/XCArchiveResult.h @@ -14,7 +14,7 @@ typedef enum : NSUInteger { XCArchiveResultArchiveSucceeded, XCArchiveResultExportFailed, XCArchiveResultExportSucceeded, - XCArchiveResultTypeCheckDependencies + XCArchiveResultTypeCheckPrint } XCArchiveResultType; @interface XCArchiveResult : NSObject diff --git a/AppBox/Common/UpdateHandler/UpdateHandler.m b/AppBox/Common/UpdateHandler/UpdateHandler.m index 7006a840..3028b40f 100644 --- a/AppBox/Common/UpdateHandler/UpdateHandler.m +++ b/AppBox/Common/UpdateHandler/UpdateHandler.m @@ -32,7 +32,7 @@ + (void)showAlreadyUptoDateAlert{ + (void)isNewVersionAvailableCompletion:(void (^)(bool available, NSURL *url))completion{ @try { - [[AppDelegate appDelegate] addSessionLog:@"Checking for new version..."]; + [ABLog log:@"Checking for new version..."]; [NetworkHandler requestWithURL:abGitHubLatestRelease withParameters:nil andRequestType:RequestGET andCompletetion:^(id responseObj, NSError *error) { //handle error and check for all required keys if (error == nil && @@ -48,7 +48,7 @@ + (void)isNewVersionAvailableCompletion:(void (^)(bool available, NSURL *url))co NSString *currentVersion = [[versionString componentsSeparatedByCharactersInSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]] componentsJoinedByString:abEmptyString]; //log current and latest version - [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"Current Version - %@ <=> Latest Version - %@", versionString, tag]]; + [ABLog log:@"Current Version - %@ <=> Latest Version - %@", versionString, tag]; //return result based on version strings completion(([newVesion compare:currentVersion] == NSOrderedDescending),[NSURL URLWithString:[responseObj valueForKey:@"html_url"]]); diff --git a/AppBox/Common/UploadManager/UploadManager.m b/AppBox/Common/UploadManager/UploadManager.m index 3abadd8a..a6f1e5c2 100644 --- a/AppBox/Common/UploadManager/UploadManager.m +++ b/AppBox/Common/UploadManager/UploadManager.m @@ -33,13 +33,14 @@ +(void)setupDBClientsManager{ #pragma mark - UnZip IPA File -(void)uploadIPAFile:(NSURL *)ipaFileURL{ - [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"\n\n======\n Preparing to Upload IPA - %@\n======\n\n",ipaFileURL]]; + [ABLog log:@"Preparing to Upload IPA - %@", ipaFileURL]; NSString *ipaPath = [ipaFileURL.resourceSpecifier stringByRemovingPercentEncoding]; if ([[NSFileManager defaultManager] fileExistsAtPath:ipaPath]) { - [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"\n\n======\nUploading IPA - %@\n======\n\n",ipaPath]]; + [ABLog log:@"nUploading IPA - %@", ipaPath]; //Unzip ipa __block NSString *payloadEntry; __block NSString *infoPlistPath; + [[AppDelegate appDelegate] addSessionLog:@"Extracting Files..."]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ [SSZipArchive unzipFileAtPath:ipaPath toDestination:NSTemporaryDirectory() overwrite:YES password:nil progressHandler:^(NSString * _Nonnull entry, unz_file_info zipInfo, long entryNumber, long total) { @@ -47,30 +48,36 @@ -(void)uploadIPAFile:(NSURL *)ipaFileURL{ [self showStatus:@"Extracting files..." andShowProgressBar:YES withProgress:-1]; //Get payload entry - if ((entry.lastPathComponent.length > 4) && [[entry.lastPathComponent substringFromIndex:(entry.lastPathComponent.length-4)].lowercaseString isEqualToString: @".app"]) { - [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"Found payload at path = %@",entry]]; - payloadEntry = entry; + if (payloadEntry == nil && [entry containsString:@".app"]) { + [[entry pathComponents] enumerateObjectsUsingBlock:^(NSString * _Nonnull pathComponent, NSUInteger idx, BOOL * _Nonnull stop) { + if ((pathComponent.length > 4) && [[pathComponent substringFromIndex:(pathComponent.length-4)].lowercaseString isEqualToString: @".app"]) { + + [ABLog log:@"Found payload at path = %@",entry]; + payloadEntry = [NSString pathWithComponents:[[entry pathComponents] subarrayWithRange:NSMakeRange(0, idx+1)]]; + *stop = YES; + } + }]; } //Get Info.plist entry - NSString *mainInfoPlistPath = [NSString stringWithFormat:@"%@Info.plist",payloadEntry].lowercaseString; + NSString *mainInfoPlistPath = [payloadEntry stringByAppendingPathComponent:@"Info.plist"].lowercaseString; if ([entry.lowercaseString isEqualToString:mainInfoPlistPath]) { - [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"Found Info.plist at path = %@",mainInfoPlistPath]]; + [ABLog log:@"Found Info.plist at path = %@",mainInfoPlistPath]; infoPlistPath = entry; } //Get embedded mobile provision if (self.project.mobileProvision == nil){ - NSString *mobileProvisionPath = [NSString stringWithFormat:@"%@embedded.mobileprovision",payloadEntry].lowercaseString; + NSString *mobileProvisionPath = [payloadEntry stringByAppendingPathComponent:@"embedded.mobileprovision"].lowercaseString; if ([entry.lowercaseString isEqualToString:mobileProvisionPath]){ - [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"Found mobileprovision at path = %@",mobileProvisionPath]]; + [ABLog log:@"Found mobileprovision at path = %@",mobileProvisionPath]; mobileProvisionPath = [NSTemporaryDirectory() stringByAppendingPathComponent: mobileProvisionPath]; self.project.mobileProvision = [[MobileProvision alloc] initWithPath:mobileProvisionPath]; } } //show status and log files entry - [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"%@-%@-%@",[NSNumber numberWithLong:entryNumber], [NSNumber numberWithLong:total], entry]]; + [ABLog log:@"%@-%@-%@",[NSNumber numberWithLong:entryNumber], [NSNumber numberWithLong:total], entry]; }); } completionHandler:^(NSString * _Nonnull path, BOOL succeeded, NSError * _Nonnull error) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -167,7 +174,7 @@ -(void)uploadIPAFileWithoutUnzip:(NSURL *)ipaURL{ } } - [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"\n\n======\nIPA Info.plist\n======\n\n - %@",self.project.ipaInfoPlist]]; + [ABLog log:@"IPA Info.plist %@", self.project.ipaInfoPlist]; //upload ipa self.dbFileType = DBFileTypeIPA; @@ -178,8 +185,7 @@ -(void)uploadIPAFileWithoutUnzip:(NSURL *)ipaURL{ [self dbUploadFile:ipaURL.resourceSpecifier.stringByRemovingPercentEncoding to:self.project.dbIPAFullPath.absoluteString mode:[[DBFILESWriteMode alloc] initWithOverwrite]]; }]; } - - [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"Temporaray folder %@",NSTemporaryDirectory()]]; + [ABLog log:@"Temporaray Folder %@", NSTemporaryDirectory()]; } @@ -213,7 +219,7 @@ -(void)handleAfterUniqueJsonMetaDataLoaded{ -(NSDictionary *)getUniqueJsonDict{ NSError *error; NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfFile:[NSTemporaryDirectory() stringByAppendingPathComponent:FILE_NAME_UNIQUE_JSON]] options:kNilOptions error:&error]; - [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"%@ : %@",FILE_NAME_UNIQUE_JSON,dictionary]]; + [ABLog log:@"%@ : %@",FILE_NAME_UNIQUE_JSON,dictionary]; return dictionary; } @@ -292,7 +298,7 @@ -(void)loadAppInfoMetaData{ [[[DBClientsManager authorizedClient].filesRoutes listRevisions:self.project.dbAppInfoJSONFullPath.absoluteString mode:revisionMode limit:@1 ] setResponseBlock:^(DBFILESListRevisionsResult * _Nullable response, DBFILESListRevisionsError * _Nullable routeError, DBRequestError * _Nullable error) { //check there is any rev available if (response && response.isDeleted.boolValue == NO && response.entries.count > 0){ - [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"Loaded Meta Data %@",response]]; + [ABLog log:@"Loaded Meta Data %@",response]; self.project.uniqueLinkJsonMetaData = [response.entries firstObject]; } @@ -305,7 +311,7 @@ -(void)loadAppInfoMetaData{ #pragma mark - Upload Files -(void)dbUploadLargeFile:(NSString *)file to:(NSString *)path mode:(DBFILESWriteMode *)mode{ offset = 0; - chunkSize = 1 * 1024 * 1024; + chunkSize = [UserData uploadChunkSize] * abBytesToMB; ipaFileData = [NSData dataWithContentsOfFile:file]; fileHandle = [NSFileHandle fileHandleForReadingAtPath:file]; nextChunkToUpload = [fileHandle readDataOfLength:chunkSize]; @@ -316,12 +322,15 @@ -(void)dbUploadLargeFile:(NSString *)file to:(NSString *)path mode:(DBFILESWrite sessionId = result.sessionId; offset += nextChunkToUpload.length; [self uploadNextChunk]; - } else if (networkError .nsError.code == -1009) { - self.lastfailedOperation = [NSBlockOperation blockOperationWithBlock:^{ - [self dbUploadLargeFile:file to:path mode:mode]; - }]; - } else if (networkError) { - [DBErrorHandler handleNetworkErrorWith:networkError]; + } else { + if (networkError.nsError.code == -1009) { + self.lastfailedOperation = [NSBlockOperation blockOperationWithBlock:^{ + [self dbUploadLargeFile:file to:path mode:mode]; + }]; + } else if (networkError) { + self.errorBlock(nil, YES); + [DBErrorHandler handleNetworkErrorWith:networkError]; + } } }] setProgressBlock:^(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) { [self updateProgressBytesWritten:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite]; @@ -342,14 +351,19 @@ -(void)uploadNextChunk{ //create shared url for ipa [self dbCreateSharedURLForFile:result.pathDisplay]; } - } else if (networkError.nsError.code == -1009) { - self.lastfailedOperation = [NSBlockOperation blockOperationWithBlock:^{ - [self uploadNextChunk]; - }]; - } else if (routeError) { - [DBErrorHandler handleUploadSessionFinishError:routeError]; } else { - [DBErrorHandler handleNetworkErrorWith:networkError]; + if (networkError.nsError.code == -1009) { + self.lastfailedOperation = [NSBlockOperation blockOperationWithBlock:^{ + [self uploadNextChunk]; + }]; + } else { + self.errorBlock(nil, YES); + if (routeError) { + [DBErrorHandler handleUploadSessionFinishError:routeError]; + } else { + [DBErrorHandler handleNetworkErrorWith:networkError]; + } + } } }] setProgressBlock:^(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) { [self updateProgressBytesWritten:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite]; @@ -359,14 +373,19 @@ -(void)uploadNextChunk{ if (result) { offset += nextChunkToUpload.length; [self uploadNextChunk]; - } else if (networkError.nsError.code == -1009) { - self.lastfailedOperation = [NSBlockOperation blockOperationWithBlock:^{ - [self uploadNextChunk]; - }]; - }else if (routeError) { - [DBErrorHandler handleUploadSessionLookupError:routeError]; } else { - [DBErrorHandler handleNetworkErrorWith:networkError]; + if (networkError.nsError.code == -1009) { + self.lastfailedOperation = [NSBlockOperation blockOperationWithBlock:^{ + [self uploadNextChunk]; + }]; + }else{ + self.errorBlock(nil, YES); + if (routeError) { + [DBErrorHandler handleUploadSessionLookupError:routeError]; + } else { + [DBErrorHandler handleNetworkErrorWith:networkError]; + } + } } }] setProgressBlock:^(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) { [self updateProgressBytesWritten:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite]; @@ -662,7 +681,7 @@ -(void)deleteBuildDetailsFromAppInfoJSON{ #pragma mark - Show Status -(void)showStatus:(NSString *)status andShowProgressBar:(BOOL)showProgressBar withProgress:(double)progress{ //log status in session log - [[AppDelegate appDelegate]addSessionLog:[NSString stringWithFormat:@"%@",status]]; + [ABLog log:@"%@",status]; //start/stop/progress based on showProgressBar and progress if (progress == -1){ diff --git a/AppBox/Common/UserData/UserData.h b/AppBox/Common/UserData/UserData.h index e9140c94..c0efcd20 100644 --- a/AppBox/Common/UserData/UserData.h +++ b/AppBox/Common/UserData/UserData.h @@ -44,4 +44,10 @@ +(BOOL)isFirstTime; +(void)setIsFirstTime:(BOOL)isFirstTime; ++(NSInteger)uploadChunkSize; ++(void)setUploadChunkSize:(NSInteger)chunkSize; + ++(BOOL)debugLog; ++(void)setEnableDebugLog:(BOOL)debugLog; + @end diff --git a/AppBox/Common/UserData/UserData.m b/AppBox/Common/UserData/UserData.m index c9153832..85711ade 100644 --- a/AppBox/Common/UserData/UserData.m +++ b/AppBox/Common/UserData/UserData.m @@ -176,4 +176,26 @@ +(void)setIsFirstTime:(BOOL)isFirstTime{ [[NSUserDefaults standardUserDefaults] synchronize]; } +#pragma mark - Chunk Size - +#define UploadChunkSize @"UploadChunkSize" ++(NSInteger)uploadChunkSize{ + NSInteger chunkSize = [[NSUserDefaults standardUserDefaults] integerForKey:UploadChunkSize]; + return chunkSize > 0 ? chunkSize : 100; +} + ++(void)setUploadChunkSize:(NSInteger)chunkSize{ + [[NSUserDefaults standardUserDefaults] setInteger:chunkSize forKey:UploadChunkSize]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + +#define DebugLogEnable @"DebugLogEnable" ++(BOOL)debugLog{ + return [[NSUserDefaults standardUserDefaults] boolForKey:DebugLogEnable]; +} + ++(void)setEnableDebugLog:(BOOL)debugLog{ + [[NSUserDefaults standardUserDefaults] boolForKey:DebugLogEnable]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + @end diff --git a/AppBox/Model/ProjectModel/XCProject.m b/AppBox/Model/ProjectModel/XCProject.m index 49fc71fc..a71a1bb1 100644 --- a/AppBox/Model/ProjectModel/XCProject.m +++ b/AppBox/Model/ProjectModel/XCProject.m @@ -206,7 +206,6 @@ - (void)setBuildListInfo:(NSDictionary *)buildListInfo{ [self setName: [projectInfo valueForKey:@"name"]]; [self setSchemes: [projectInfo valueForKey:@"schemes"]]; [self setTargets: [projectInfo valueForKey:@"targets"]]; - [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"\n\n======\nBuild List Info\n======\n\n %@",buildListInfo]]; } } diff --git a/AppBox/ViewController/HomeViewController/HomeViewController.m b/AppBox/ViewController/HomeViewController/HomeViewController.m index b031d0f2..a6c6c8c7 100644 --- a/AppBox/ViewController/HomeViewController/HomeViewController.m +++ b/AppBox/ViewController/HomeViewController/HomeViewController.m @@ -10,11 +10,11 @@ @implementation HomeViewController{ XCProject *project; - XCProject *repoProject; + XCProject *ciRepoProject; ScriptType scriptType; NSArray *allTeamIds; UploadManager *uploadManager; - NSInteger schemeScriptRunCount; + NSInteger processExecuteCount; } - (void)viewDidLoad { @@ -108,9 +108,9 @@ -(void)setupUploadManager{ #pragma mark - Build Repo - (void)initBuildRepoProcess:(NSNotification *)notification { if ([notification.object isKindOfClass:[XCProject class]]) { - repoProject = notification.object; + ciRepoProject = notification.object; [tabView selectTabViewItem:tabView.tabViewItems.firstObject]; - [self initProjectBuildProcessForURL: repoProject.fullPath]; + [self initProjectBuildProcessForURL: ciRepoProject.fullPath]; } } @@ -325,6 +325,9 @@ - (void)runBuildScript{ } [buildArgument addObject:version]; + NSString *xcPrettyPath = [[NSBundle mainBundle] pathForResource:@"xcpretty/bin/xcpretty" ofType:nil]; + [buildArgument addObject:xcPrettyPath]; + //Run Task [self runTaskWithLaunchPath:buildScriptPath andArgument:buildArgument]; }); @@ -375,9 +378,9 @@ - (void)runTaskWithLaunchPath:(NSString *)launchPath andArgument:(NSArray *)argu [self captureStandardOutputWithTask:task]; [task launch]; if (scriptType == ScriptTypeTeamId){ - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [task terminate]; - [[AppDelegate appDelegate] addSessionLog:@"terminating task!!"]; + [ABLog log:@"terminating task!!"]; }); } } @@ -390,7 +393,7 @@ - (void)captureStandardOutputWithTask:(NSTask *)task{ [[NSNotificationCenter defaultCenter] addObserverForName:NSFileHandleDataAvailableNotification object:outputPipe.fileHandleForReading queue:nil usingBlock:^(NSNotification * _Nonnull note) { NSData *outputData = outputPipe.fileHandleForReading.availableData; NSString *outputString = [[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding]; - [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"Task Output - %@\n",outputString]]; + [[AppDelegate appDelegate] addSessionLog:outputString]; dispatch_async(dispatch_get_main_queue(), ^{ //Handle Project Scheme Response @@ -404,13 +407,9 @@ - (void)captureStandardOutputWithTask:(NSTask *)task{ } if (buildList != nil && comboBuildScheme.numberOfItems > 0){ [comboBuildScheme selectItemAtIndex:0]; - if (repoProject == nil) { - [self comboBuildSchemeValueChanged:comboBuildScheme]; - - //Run Team Id Script - [self runTeamIDScript]; - } else { - [RepoBuilder setProjectSettingFromProject:repoProject toProject:project]; + //If CI Project then set project details direct from appbox.plist + if (ciRepoProject) { + [RepoBuilder setProjectSettingFromProject:ciRepoProject toProject:project]; [comboTeamId removeAllItems]; [comboTeamId addItemWithObjectValue:project.teamId]; [comboTeamId selectItemWithObjectValue:project.teamId]; @@ -422,11 +421,18 @@ - (void)captureStandardOutputWithTask:(NSTask *)task{ [buttonSendMail setState:NSOnState]; } [self actionButtonTapped:buttonAction]; + } else { + [self comboBuildSchemeValueChanged:comboBuildScheme]; + [self runTeamIDScript]; } }else{ - if (schemeScriptRunCount == 3){ - schemeScriptRunCount = 0; + if (processExecuteCount == 3){ + processExecuteCount = 0; [self viewStateForProgressFinish:YES]; + //exit if appbox failed to load scheme information for ci project + if (ciRepoProject) { + exit(1); + } NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText: @"Failed to load scheme information."]; [alert setInformativeText:@"Please try again with shared Xcode project schemes."]; @@ -437,7 +443,7 @@ - (void)captureStandardOutputWithTask:(NSTask *)task{ [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:abShareXcodeProjectSchemeURL]]; } } else { - schemeScriptRunCount++; + processExecuteCount++; [self runGetSchemeScript]; [[AppDelegate appDelegate] addSessionLog:@"Failed to load scheme information."]; } @@ -472,34 +478,6 @@ - (void)captureStandardOutputWithTask:(NSTask *)task{ //Handle Build Response else if (scriptType == ScriptTypeBuild){ - - XCArchiveResult *result = [XCArchiveParser archiveResultMessageFromString:outputString]; - switch (result.type) { - case XCArchiveResultCleanSucceeded:{ - - }break; - - case XCArchiveResultArchiveFailed:{ - - }break; - - case XCArchiveResultArchiveSucceeded:{ - - }break; - - case XCArchiveResultExportFailed:{ - - }break; - - case XCArchiveResultExportSucceeded:{ - - }break; - - default: - break; - } - - if ([outputString.lowercaseString containsString:@"archive succeeded"]){ [self showStatus:@"Creating IPA..." andShowProgressBar:YES withProgress:-1]; [outputPipe.fileHandleForReading waitForDataInBackgroundAndNotify]; @@ -514,16 +492,23 @@ - (void)captureStandardOutputWithTask:(NSTask *)task{ [self showStatus:@"Export Succeeded" andShowProgressBar:YES withProgress:-1]; [self checkIPACreated]; } - } else if ([outputString.lowercaseString containsString:@"export failed"]){ [self showStatus:@"Export Failed" andShowProgressBar:NO withProgress:-1]; [Common showAlertWithTitle:@"Export Failed" andMessage:outputString]; [self viewStateForProgressFinish:YES]; + //exit if appbox failed to export IPA file + if (ciRepoProject) { + exit(1); + } } else if ([outputString.lowercaseString containsString:@"archive failed"]){ if ([AppDelegate appDelegate].isInternetConnected || [outputString containsString:@"^"]){ [self showStatus:@"Archive Failed" andShowProgressBar:NO withProgress:-1]; [Common showAlertWithTitle:@"Archive Failed" andMessage:outputString]; [self viewStateForProgressFinish:YES]; + //exit if appbox failed to archive the project + if (ciRepoProject) { + exit(1); + } }else{ [self showStatus:abNotConnectedToInternet andShowProgressBar:YES withProgress:-1]; uploadManager.lastfailedOperation = [NSBlockOperation blockOperationWithBlock:^{ @@ -630,7 +615,7 @@ - (void)dropboxLogoutHandler:(id)sender{ #pragma mark - Controller Helpers - -(void)viewStateForProgressFinish:(BOOL)finish{ - [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"Updating view setting for finish - %@", [NSNumber numberWithBool:finish]]]; + [ABLog log:@"Updating view setting for finish - %@", [NSNumber numberWithBool:finish]]; [[AppDelegate appDelegate] setProcessing:!finish]; [[AppDelegate appDelegate] setIsReadyToBuild:!finish]; @@ -841,6 +826,8 @@ -(void)logAppUploadEventAndShareURLOnSlackChannel{ NSDictionary *currentSetting = [self getBasicViewStateWithOthersSettings:@{@"Uploaded to":@"Dropbox"}]; [EventTracker logEventSettingWithType:LogEventSettingTypeUploadIPASuccess andSettings:currentSetting]; + [[AppDelegate appDelegate] addSessionLog:[NSString stringWithFormat:@"\n\n\nBUILD URL - %@\n\n\n", project.appShortShareableURL]]; + if ([UserData userSlackMessage].length > 0) { [self showStatus:@"Sending Message on Slack..." andShowProgressBar:YES withProgress:-1]; [SlackClient sendMessageForProject:project completion:^(BOOL success) { @@ -867,7 +854,7 @@ -(void) handleAppURLAfterSlack { }); }else if(![self.presentedViewControllers.lastObject isKindOfClass:[ShowLinkViewController class]]){ //if mac shutdown isn't checked then show link - if (repoProject == nil){ + if (ciRepoProject == nil){ [self performSegueWithIdentifier:@"ShowLink" sender:self]; }else{ [self viewStateForProgressFinish:YES]; diff --git a/AppBox/ViewController/PreferencesViewController/CISettingViewController/CISettingViewController.xib b/AppBox/ViewController/PreferencesViewController/CISettingViewController/CISettingViewController.xib index 02e4ffff..f2d340db 100644 --- a/AppBox/ViewController/PreferencesViewController/CISettingViewController/CISettingViewController.xib +++ b/AppBox/ViewController/PreferencesViewController/CISettingViewController/CISettingViewController.xib @@ -50,8 +50,8 @@ - + - + + + 10 MB + 25 MB + 50 MB + 75 MB + 100 MB + 125 MB + 150 MB + @@ -200,6 +209,7 @@ + diff --git a/AppBox/ViewController/PreferencesViewController/PreferencesTabViewController.m b/AppBox/ViewController/PreferencesViewController/PreferencesTabViewController.m index 3bb1c65e..447994fe 100644 --- a/AppBox/ViewController/PreferencesViewController/PreferencesTabViewController.m +++ b/AppBox/ViewController/PreferencesViewController/PreferencesTabViewController.m @@ -69,7 +69,7 @@ - (void)viewWillAppear { NSTabViewItem *ciPreferencesTabViewItem = [[NSTabViewItem alloc] initWithIdentifier:@"ci"]; [ciPreferencesTabViewItem setLabel:@"CI Setting"]; - [ciPreferencesTabViewItem setImage:[NSImage imageNamed:@"BlueHelp"]]; + [ciPreferencesTabViewItem setImage:[NSImage imageNamed:@"CI"]]; [ciPreferencesTabViewItem setViewController:ciSettingViewController]; [self addTabViewItem:ciPreferencesTabViewItem];