├── .gitignore ├── .gitmodules ├── Core ├── Helpers │ ├── GetIPAddress.h │ ├── GetIPAddress.m │ ├── GetPrimaryMACAddress.h │ ├── GetPrimaryMACAddress.mm │ ├── Logger │ │ ├── MYPackageFileLogger.h │ │ ├── MYPackageFileLogger.m │ │ ├── MYPackageLogger.h │ │ ├── MYPackageLogger.m │ │ ├── MYPackageMultiLogger.h │ │ └── MYPackageMultiLogger.m │ ├── MYPackageWorkspaceParser.h │ ├── MYPackageWorkspaceParser.m │ ├── QRCode │ │ ├── GDQrCodeConst.h │ │ ├── NSColor+GDQrCode.h │ │ ├── NSColor+GDQrCode.m │ │ ├── NSImage+CGImage.h │ │ ├── NSImage+CGImage.m │ │ ├── NSImage+GDQrCodeImage.h │ │ └── NSImage+GDQrCodeImage.m │ ├── SafeThread.h │ └── SafeThread.m ├── Model │ ├── MYPackageConfig.h │ ├── MYPackageConfig.m │ ├── MYPackageProfile.h │ ├── MYPackageProfile.m │ ├── MYPackageProject.h │ ├── MYPackageProject.m │ ├── MYPackageScheme.h │ ├── MYPackageScheme.m │ ├── MYPackageTarget.h │ ├── MYPackageTarget.m │ ├── MYPackageTargetDefine.h │ ├── MYPackageTargetDefine.m │ ├── MYPackageWorkspace.h │ └── MYPackageWorkspace.m └── Task │ ├── Clean │ ├── MYPackageCleanFinalProductTask.h │ ├── MYPackageCleanFinalProductTask.m │ ├── MYPackageCleanIntermediateProductTask.h │ └── MYPackageCleanIntermediateProductTask.m │ ├── Compile │ ├── MYPackageBuildAppTask.h │ ├── MYPackageBuildAppTask.m │ ├── MYPackageBuildLibTask.h │ ├── MYPackageBuildLibTask.m │ ├── MYPackageCleanTask.h │ ├── MYPackageCleanTask.m │ ├── MYPackageLipoTask.h │ ├── MYPackageLipoTask.m │ └── Shell │ │ ├── build.xcconfig │ │ ├── create-scheme.rb │ │ └── lipo.sh │ ├── MYPackageBaseTask.h │ ├── MYPackageBaseTask.m │ ├── MYPackageShellTask.h │ ├── MYPackageShellTask.m │ ├── MYPackageTaskManager+TaskList.h │ ├── MYPackageTaskManager+TaskList.m │ ├── MYPackageTaskManager.h │ ├── MYPackageTaskManager.m │ ├── Package │ ├── Feature │ │ ├── MYPackageIPAInfoTask.h │ │ ├── MYPackageIPAInfoTask.m │ │ ├── MYPackageListProfilesTask.h │ │ └── MYPackageListProfilesTask.m │ ├── MYPackageAnalyzeProductTask.h │ ├── MYPackageAnalyzeProductTask.m │ ├── MYPackageCalculateSHA1Task.h │ ├── MYPackageCalculateSHA1Task.m │ ├── MYPackageCreateDistributePlistTask.h │ ├── MYPackageCreateDistributePlistTask.m │ ├── MYPackageCreateSpecTask.h │ ├── MYPackageCreateSpecTask.m │ ├── MYPackageResignAppTask.h │ ├── MYPackageResignAppTask.m │ ├── MYPackageUpdatePlistTask.h │ ├── MYPackageUpdatePlistTask.m │ ├── MYPackageZipTask.h │ ├── MYPackageZipTask.m │ └── Shell │ │ ├── list_provisioning_profiles.rb │ │ ├── parse_provisioning_profile.rb │ │ └── xcode-analyze.rb │ ├── Prepare │ ├── Feature │ │ ├── MYPackageCleanInvalidCertTask.h │ │ ├── MYPackageCleanInvalidCertTask.m │ │ ├── MYPackageCleanInvalidProfileTask.h │ │ ├── MYPackageCleanInvalidProfileTask.m │ │ ├── MYPackageGetHighestVersionTask.h │ │ ├── MYPackageGetHighestVersionTask.m │ │ ├── MYPackageIPAInfoTask.h │ │ ├── MYPackageIPAInfoTask.m │ │ ├── MYPackageListOpenedProjectTask.h │ │ ├── MYPackageListOpenedProjectTask.m │ │ ├── MYPackageListProfilesTask.h │ │ └── MYPackageListProfilesTask.m │ ├── MYPackageAnalyzeGitTask.h │ ├── MYPackageAnalyzeGitTask.m │ ├── MYPackageAnalyzeProjectTask.h │ ├── MYPackageAnalyzeProjectTask.m │ ├── MYPackageAnalyzeSchemeTask.h │ ├── MYPackageAnalyzeSchemeTask.m │ ├── MYPackageAnalyzeTargetTask.h │ ├── MYPackageAnalyzeTargetTask.m │ ├── MYPackageCheckEnvironmentTask.h │ ├── MYPackageCheckEnvironmentTask.m │ ├── MYPackageCheckGitTask.h │ ├── MYPackageCheckGitTask.m │ ├── MYPackageInitTask.h │ ├── MYPackageInitTask.m │ ├── MYPackageListSchemeTask.h │ ├── MYPackageListSchemeTask.m │ ├── MYPackagePrepareEnvironmentTask.h │ ├── MYPackagePrepareEnvironmentTask.m │ └── Script │ │ ├── GemEnv │ │ ├── .bundle │ │ │ └── config │ │ ├── Gemfile │ │ ├── Gemfile.lock │ │ └── vendor │ │ │ └── cache │ │ │ ├── CFPropertyList-3.0.0.gem │ │ │ ├── activesupport-4.2.10.gem │ │ │ ├── atomos-0.1.2.gem │ │ │ ├── claide-1.0.2.gem │ │ │ ├── cocoapods-1.5.3.gem │ │ │ ├── cocoapods-core-1.5.3.gem │ │ │ ├── cocoapods-deintegrate-1.0.2.gem │ │ │ ├── cocoapods-downloader-1.2.1.gem │ │ │ ├── cocoapods-plugins-1.0.0.gem │ │ │ ├── cocoapods-search-1.0.0.gem │ │ │ ├── cocoapods-stats-1.0.0.gem │ │ │ ├── cocoapods-trunk-1.3.0.gem │ │ │ ├── cocoapods-try-1.1.0.gem │ │ │ ├── colored2-3.1.2.gem │ │ │ ├── concurrent-ruby-1.0.5.gem │ │ │ ├── escape-0.0.4.gem │ │ │ ├── fourflusher-2.0.1.gem │ │ │ ├── fuzzy_match-2.0.4.gem │ │ │ ├── gh_inspector-1.1.3.gem │ │ │ ├── i18n-0.9.5.gem │ │ │ ├── minitest-5.11.3.gem │ │ │ ├── molinillo-0.6.5.gem │ │ │ ├── nanaimo-0.2.5.gem │ │ │ ├── nap-1.1.0.gem │ │ │ ├── netrc-0.11.0.gem │ │ │ ├── plist-3.4.0.gem │ │ │ ├── rouge-2.0.7.gem │ │ │ ├── ruby-macho-1.1.0.gem │ │ │ ├── thread_safe-0.3.6.gem │ │ │ ├── tzinfo-1.2.5.gem │ │ │ ├── xcodeproj-1.5.9.gem │ │ │ └── xcpretty-0.2.8.gem │ │ ├── RubyEnv │ │ ├── bundler-1.16.2.gem │ │ └── rubygems-2.7.7.zip │ │ ├── analyze_scheme.rb │ │ ├── clean_invalid_cert.rb │ │ ├── clean_invalid_profile.rb │ │ ├── find_highest_version_in_repo.rb │ │ ├── install_p12.sh │ │ ├── setup_gem_env.sh │ │ └── setup_ruby_env.sh │ └── Release │ ├── Adhoc.plist │ ├── MYPackageCreateTagTask.h │ ├── MYPackageCreateTagTask.m │ ├── MYPackageUploadLocalTask.h │ ├── MYPackageUploadLocalTask.m │ └── index.html ├── Gemfile ├── Gemfile.lock ├── Package.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── Package.xcscheme ├── Package.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── WorkspaceSettings.xcsettings ├── Package ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── cb_mono_off.imageset │ │ ├── Contents.json │ │ ├── cb_mono_off.png │ │ └── cb_mono_off@2x.png │ └── cb_mono_on.imageset │ │ ├── Contents.json │ │ ├── cb_mono_on.png │ │ └── cb_mono_on@2x.png ├── Base.lproj │ └── MainMenu.xib ├── CommandLine │ ├── MYPackageCommandLine.h │ └── MYPackageCommandLine.m ├── Info.plist ├── PackageAppDelegate.h ├── PackageAppDelegate.m ├── PrefixHeader.pch ├── Server │ ├── Cert │ │ ├── create_ca_root_cert.sh │ │ ├── create_self_sign_cert.sh │ │ ├── myCA.cer │ │ └── myCA.key │ ├── MYPackageServer.h │ └── MYPackageServer.m ├── View │ ├── NSView+Enable.h │ └── NSView+Enable.m ├── ViewController │ ├── Base │ │ ├── MYPackageBaseViewController.h │ │ ├── MYPackageBaseViewController.m │ │ ├── MYPackageDetailLabel.h │ │ ├── MYPackageDetailLabel.m │ │ ├── MYPackageTableRowView.h │ │ ├── MYPackageTableRowView.m │ │ ├── MYPackageTitleLabel.h │ │ └── MYPackageTitleLabel.m │ ├── BuildIPA │ │ ├── MYPackageBuildAppViewController.h │ │ ├── MYPackageBuildAppViewController.m │ │ ├── MYPackageBuildAppViewController.xib │ │ ├── MYPackageQRCodeViewController.h │ │ ├── MYPackageQRCodeViewController.m │ │ └── MYPackageQRCodeViewController.xib │ ├── Container │ │ ├── MYPackageContainerViewController.h │ │ ├── MYPackageContainerViewController.m │ │ └── MYPackageContainerViewController.xib │ ├── PrepareEnvironment │ │ ├── MYPackagePrepareEnvironmentViewController.h │ │ ├── MYPackagePrepareEnvironmentViewController.m │ │ └── MYPackagePrepareEnvironmentViewController.xib │ ├── SelectProject │ │ ├── MYPackageSelectProjectTableViewHeader.h │ │ ├── MYPackageSelectProjectTableViewHeader.m │ │ ├── MYPackageSelectProjectTableViewHeader.xib │ │ ├── MYPackageSelectProjectTableViewRow.h │ │ ├── MYPackageSelectProjectTableViewRow.m │ │ ├── MYPackageSelectProjectTableViewRow.xib │ │ ├── MYPackageSelectProjectViewController.h │ │ ├── MYPackageSelectProjectViewController.m │ │ └── MYPackageSelectProjectViewController.xib │ ├── SelectScheme │ │ ├── MYPackageSelectSchemeTableViewRow.h │ │ ├── MYPackageSelectSchemeTableViewRow.m │ │ ├── MYPackageSelectSchemeTableViewRow.xib │ │ ├── MYPackageSelectSchemeViewController.h │ │ ├── MYPackageSelectSchemeViewController.m │ │ └── MYPackageSelectSchemeViewController.xib │ └── SelectTask │ │ ├── MYPackageSelectTaskViewController.h │ │ ├── MYPackageSelectTaskViewController.m │ │ └── MYPackageSelectTaskViewController.xib ├── auto-update-build-version.sh └── main.m ├── Podfile ├── Podfile.lock ├── README.md └── XcodePackage.podspec /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .hgignore 3 | DerivedData/ 4 | build/ 5 | .hg/* 6 | .svn/ 7 | *.moved-aside 8 | *.swp 9 | 10 | # Xcode 11 | *.pbxuser 12 | *.mode1v3 13 | *.mode2v3 14 | *.perspectivev3 15 | *.xcuserstate 16 | *.xccheckout 17 | *.xcscmblueprint 18 | project.xcworkspace/ 19 | xcuserdata/ 20 | 21 | .idea/ 22 | 23 | 24 | # CocoaPods 25 | Pods/ 26 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ObjCCommandLine"] 2 | path = ObjCCommandLine 3 | url = git@github.com:dijkst/ObjCCommandLine.git 4 | -------------------------------------------------------------------------------- /Core/Helpers/GetIPAddress.h: -------------------------------------------------------------------------------- 1 | // 2 | // GetIPAddress.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/4/18. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #ifndef GetIPAddress_h 10 | #define GetIPAddress_h 11 | 12 | #import 13 | 14 | FOUNDATION_EXPORT NSString *IPAddress(void); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /Core/Helpers/GetIPAddress.m: -------------------------------------------------------------------------------- 1 | // 2 | // GetIPAddress.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/4/18. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "GetIPAddress.h" 10 | #import 11 | #import 12 | #import 13 | 14 | NSString *IPAddress() { 15 | NSString *address = @"error"; 16 | struct ifaddrs *interfaces = NULL; 17 | struct ifaddrs *temp_addr = NULL; 18 | int success = 0; 19 | 20 | // retrieve the current interfaces - returns 0 on success 21 | success = getifaddrs(&interfaces); 22 | if (success == 0) { 23 | // Loop through linked list of interfaces 24 | temp_addr = interfaces; 25 | while (temp_addr != NULL) { 26 | if (temp_addr->ifa_addr->sa_family == AF_INET) { 27 | // Get NSString from C String 28 | address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)]; 29 | } 30 | temp_addr = temp_addr->ifa_next; 31 | } 32 | } 33 | 34 | // Free memory 35 | freeifaddrs(interfaces); 36 | return address; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /Core/Helpers/GetPrimaryMACAddress.h: -------------------------------------------------------------------------------- 1 | // 2 | // GetPrimaryMACAddress.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/3/30. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #ifndef GetPrimaryMACAddress_h 10 | #define GetPrimaryMACAddress_h 11 | 12 | #import 13 | 14 | FOUNDATION_EXPORT NSString *macAddress(void); 15 | 16 | #endif /* GetPrimaryMACAddress_h */ 17 | -------------------------------------------------------------------------------- /Core/Helpers/Logger/MYPackageFileLogger.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageFileLogger.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/12. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MYPackageLogger.h" 11 | 12 | @interface MYPackageFileLogger : MYPackageLogger { 13 | NSFileHandle *logFile; 14 | } 15 | 16 | @property (nonatomic, strong) NSString *filePath; 17 | 18 | - (void)close; 19 | 20 | - (NSString *)logText; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Core/Helpers/Logger/MYPackageFileLogger.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageFileLogger.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/12. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageFileLogger.h" 10 | 11 | @implementation MYPackageFileLogger { 12 | NSMutableString *cacheLog; 13 | } 14 | 15 | - (void)dealloc { 16 | [self close]; 17 | } 18 | 19 | - (void)close { 20 | [logFile closeFile]; 21 | logFile = nil; 22 | } 23 | 24 | - (void)setFilePath:(NSString *)filePath { 25 | if (_filePath == filePath || [_filePath isEqualToString:filePath]) { 26 | return; 27 | } 28 | if (_filePath) { 29 | [self close]; 30 | } 31 | _filePath = filePath; 32 | if (_filePath) { 33 | NSFileManager *fileManager = [NSFileManager defaultManager]; 34 | if ([fileManager fileExistsAtPath:filePath]) { 35 | [fileManager removeItemAtPath:filePath error:nil]; 36 | } 37 | [fileManager createDirectoryAtPath:[filePath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil]; 38 | [fileManager createFileAtPath:filePath 39 | contents:nil 40 | attributes:nil]; 41 | logFile = [NSFileHandle fileHandleForWritingAtPath:filePath]; 42 | [logFile seekToEndOfFile]; 43 | [self logMessage:cacheLog]; 44 | cacheLog = nil; 45 | } 46 | } 47 | 48 | - (void)logMessage:(NSString *)message { 49 | if (logFile) { 50 | if ([message length] > 0) { 51 | [logFile writeData:[message dataUsingEncoding:NSUTF8StringEncoding]]; 52 | [logFile synchronizeFile]; 53 | } 54 | } else { 55 | if (!cacheLog) { 56 | cacheLog = [message mutableCopy]; 57 | } else { 58 | [cacheLog appendString:message]; 59 | } 60 | } 61 | } 62 | 63 | - (NSString *)logText { 64 | if (!_filePath) { 65 | return cacheLog; 66 | } 67 | return [NSString stringWithContentsOfFile:_filePath encoding:NSUTF8StringEncoding error:nil]; 68 | } 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /Core/Helpers/Logger/MYPackageLogger.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageLogger.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/8/25. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MYPackageLogger : NSObject 12 | 13 | - (void)log:(NSString *)format, ...; 14 | - (void)logN:(NSString *)format, ...; 15 | - (void)logMessage:(NSString *)message; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Core/Helpers/Logger/MYPackageLogger.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageLogger.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/8/25. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageLogger.h" 10 | 11 | @implementation MYPackageLogger 12 | 13 | - (void)logN:(NSString *)format, ...{ 14 | if (!format) { 15 | return; 16 | } 17 | va_list ap; 18 | va_start(ap, format); 19 | NSString *message = [[NSString alloc] initWithFormat:format arguments:ap]; 20 | va_end(ap); 21 | [self logMessage:[message stringByAppendingString:@"\n"]]; 22 | } 23 | 24 | - (void)log:(NSString *)format, ...{ 25 | if (!format) { 26 | return; 27 | } 28 | va_list ap; 29 | va_start(ap, format); 30 | NSString *message = [[NSString alloc] initWithFormat:format arguments:ap]; 31 | va_end(ap); 32 | [self logMessage:message]; 33 | } 34 | 35 | - (void)logMessage:(NSString *)message { 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /Core/Helpers/Logger/MYPackageMultiLogger.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageMultiLogger.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/8/25. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MYPackageLogger.h" 11 | 12 | @interface MYPackageMultiLogger : MYPackageLogger 13 | 14 | @property (nonatomic, strong) NSString *filePath; 15 | @property (nonatomic, strong) NSTextView *textView; 16 | @property (nonatomic, readonly) NSString *logText; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Core/Helpers/Logger/MYPackageMultiLogger.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageMultiLogger.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/8/25. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageMultiLogger.h" 10 | #import "MYPackageFileLogger.h" 11 | 12 | @interface MYPackageMultiLogger () 13 | 14 | @property (nonatomic, strong) MYPackageFileLogger *fileLogger; 15 | 16 | @end 17 | 18 | @implementation MYPackageMultiLogger 19 | 20 | - (MYPackageFileLogger *)fileLogger { 21 | if (!_fileLogger) { 22 | _fileLogger = [[MYPackageFileLogger alloc] init]; 23 | } 24 | return _fileLogger; 25 | } 26 | 27 | - (void)setFilePath:(NSString *)filePath { 28 | [self.fileLogger setFilePath:filePath]; 29 | } 30 | 31 | - (void)logMessage:(NSString *)message { 32 | if (!message) { 33 | return; 34 | } 35 | printf("%s", [message UTF8String]); 36 | [self.fileLogger logMessage:message]; 37 | } 38 | 39 | - (NSString *)logText { 40 | return [self.fileLogger logText]; 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Core/Helpers/MYPackageWorkspaceParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageWorkspaceParser.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/5. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MYPackageWorkspaceParser : NSObject 12 | 13 | + (NSArray *)projectsInWorkspace:(NSString *)workspace; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Core/Helpers/MYPackageWorkspaceParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageWorkspaceParser.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/5. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageWorkspaceParser.h" 10 | 11 | @implementation MYPackageWorkspaceParser 12 | 13 | + (NSArray *)projectsInWorkspace:(NSString *)workspace { 14 | NSError *error = nil; 15 | NSString *xcworkspace = [[NSString alloc] initWithContentsOfFile:[workspace stringByAppendingPathComponent:@"contents.xcworkspacedata"] encoding:NSUTF8StringEncoding error:&error]; 16 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"location *= *[\"']group:(.+?)[\"']" 17 | options:NSRegularExpressionAnchorsMatchLines 18 | error:&error]; 19 | 20 | if (error) return nil; 21 | NSMutableArray *array = [NSMutableArray array]; 22 | [regex enumerateMatchesInString:xcworkspace 23 | options:0 24 | range:NSMakeRange(0, [xcworkspace length]) 25 | usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { 26 | NSRange matchRange = [result rangeAtIndex:1]; 27 | NSString *project = [xcworkspace substringWithRange:matchRange]; 28 | [array addObject:project]; 29 | }]; 30 | return array; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Core/Helpers/QRCode/GDQrCodeConst.h: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * GDQrCode中用的常量 4 | */ 5 | 6 | #define GDCodeMINSize 300 7 | #define GDCodeMAXSize 1024 8 | -------------------------------------------------------------------------------- /Core/Helpers/QRCode/NSColor+GDQrCode.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+GDQrCode.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/4/24. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef struct { 12 | CGFloat R; 13 | CGFloat G; 14 | CGFloat B; 15 | CGFloat alpa; 16 | } GDColorRGBA; 17 | 18 | @interface NSColor (GDQrCode) 19 | 20 | - (GDColorRGBA)getRGBA; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Core/Helpers/QRCode/NSColor+GDQrCode.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+GDQrCode.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/4/24. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "NSColor+GDQrCode.h" 10 | 11 | @implementation NSColor (GDQrCode) 12 | 13 | - (GDColorRGBA)getRGBA { 14 | CGFloat r = 0, g = 0, b = 0, a = 0; 15 | 16 | if ([self respondsToSelector:@selector(getRed:green:blue:alpha:)]) { 17 | NSColor *aColor = [self colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; 18 | [aColor getRed:&r green:&g blue:&b alpha:&a]; 19 | } else { 20 | const CGFloat *components = CGColorGetComponents(self.CGColor); 21 | r = components[0]; 22 | g = components[1]; 23 | b = components[2]; 24 | a = components[3]; 25 | } 26 | GDColorRGBA rgba = {r * 255.0, g * 255.0, b * 255.0, a * 255.0}; 27 | return rgba; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Core/Helpers/QRCode/NSImage+CGImage.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSImage+CGImage.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/5/19. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSImage (CGImage) 12 | 13 | @property (nonatomic, readonly) CGImageRef CGImage; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Core/Helpers/QRCode/NSImage+CGImage.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSImage+CGImage.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/5/19. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "NSImage+CGImage.h" 10 | 11 | @implementation NSImage (CGImage) 12 | 13 | - (CGImageRef)CGImage { 14 | CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)[self TIFFRepresentation], NULL); 15 | return CGImageSourceCreateImageAtIndex(source, 0, NULL); 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Core/Helpers/SafeThread.h: -------------------------------------------------------------------------------- 1 | // 2 | // SafeThread.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/5. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | 11 | void dispatch_main_sync_safe(dispatch_block_t block); 12 | void dispatch_main_async_safe(dispatch_block_t block); 13 | -------------------------------------------------------------------------------- /Core/Helpers/SafeThread.m: -------------------------------------------------------------------------------- 1 | // 2 | // SafeThread.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/5. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "SafeThread.h" 10 | 11 | void dispatch_main_sync_safe(dispatch_block_t block) { 12 | if ([[NSThread currentThread] isMainThread]) { 13 | block(); 14 | } else { 15 | dispatch_sync(dispatch_get_main_queue(), block); 16 | } 17 | } 18 | 19 | void dispatch_main_async_safe(dispatch_block_t block) { 20 | if ([[NSThread currentThread] isMainThread]) { 21 | block(); 22 | } else { 23 | dispatch_async(dispatch_get_main_queue(), block); 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /Core/Model/MYPackageConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageConfig.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/5. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MYPackageMultiLogger.h" 11 | #import "MYPackageWorkspace.h" 12 | 13 | @interface MYPackageConfig : NSObject 14 | 15 | @property (nonatomic, strong) MYPackageMultiLogger *logger; 16 | 17 | @property (nonatomic, readonly) MYPackageTarget *appTarget; 18 | @property (nonatomic, readonly) NSString *xcconfig; 19 | 20 | // IPA 放在本地服务器的路径 21 | @property (nonatomic, strong) NSString *serverPath; 22 | 23 | // for all 24 | @property (nonatomic, strong) NSString *name; 25 | @property (nonatomic, strong) NSString *bundleId; 26 | @property (nonatomic, strong) NSString *version; 27 | @property (nonatomic, readonly) BOOL isSNAPSHOT; 28 | @property (nonatomic, strong) NSString *configruation; 29 | @property (nonatomic, strong) NSString *xcconfigSettings; 30 | @property (nonatomic, strong) NSString *downloadUrl; 31 | 32 | @property (nonatomic, strong) NSString *selectedSchemeName; 33 | @property (nonatomic, strong) MYPackageScheme *selectedScheme; 34 | 35 | @property (nonatomic, strong) NSString *workspaceFilePath; 36 | @property (nonatomic, strong) MYPackageWorkspace *workspace; 37 | 38 | // for pod 39 | @property (nonatomic, strong) NSString *authorName; 40 | @property (nonatomic, strong) NSString *authorEmail; 41 | @property (nonatomic, strong) NSString *homePage; 42 | @property (nonatomic, strong) NSString *gitUrl; 43 | @property (nonatomic, strong) NSString *commitHash; 44 | 45 | // for app 46 | @property (nonatomic, strong) NSString *teamID; 47 | @property (nonatomic, strong) NSString *signType; 48 | 49 | // 以下为绝对路径 50 | @property (nonatomic, strong) NSString *outputDir; 51 | @property (nonatomic, strong) NSString *archivePath; 52 | @property (nonatomic, readonly) NSString *productsDir; 53 | @property (nonatomic, readonly) NSString *simulatorsDir; 54 | @property (nonatomic, readonly) NSString *devicesDir; 55 | @property (nonatomic, readonly) NSString *zipDir; 56 | @property (nonatomic, readonly) NSString *logPath; 57 | @property (nonatomic, readonly) NSString *specPath; 58 | @property (nonatomic, readonly) NSString *bundlePath; 59 | 60 | @property (nonatomic, strong) NSString *bundleHash; 61 | 62 | - (NSString *)productPathForTarget:(MYPackageTarget *)target; 63 | - (NSString *)simulatorPathForTarget:(MYPackageTarget *)target; 64 | - (NSString *)devicePathForTarget:(MYPackageTarget *)target; 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /Core/Model/MYPackageProfile.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageProfile.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/2/8. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM (NSInteger, MYPackageProfileType) { 12 | MYPackageProfileTypeDevelopment = 0, 13 | MYPackageProfileTypeEnterprise = 1, 14 | MYPackageProfileTypeAdhoc = 2, 15 | MYPackageProfileTypeAppStore = 3, 16 | }; 17 | 18 | NSString *profileTypeNameForType(MYPackageProfileType type); 19 | 20 | @interface MYPackageProfile : NSObject 21 | 22 | @property (nonatomic, strong) NSString *AppIDName; 23 | @property (nonatomic, strong) NSString *Name; 24 | @property (nonatomic, strong) NSString *UUID; 25 | 26 | @property (nonatomic, strong) NSArray *ApplicationIdentifierPrefix; 27 | @property (nonatomic, strong) NSArray *TeamIdentifier; 28 | @property (nonatomic, strong) NSString *TeamName; 29 | @property (nonatomic, strong) NSDictionary *Entitlements; 30 | @property (nonatomic, readonly) NSString *ApplicationIdentifier; 31 | 32 | @property (nonatomic, strong) NSString *CreationDate; 33 | @property (nonatomic, strong) NSString *ExpirationDate; 34 | 35 | @property (nonatomic, strong) NSArray *Platform; 36 | @property (nonatomic, strong) NSDictionary *DeveloperCertificates; 37 | @property (nonatomic, readonly) NSString *SignCertificate; 38 | @property (nonatomic, assign) NSInteger ProvisionedDevices; 39 | 40 | @property (nonatomic, strong) NSString *Path; 41 | 42 | @property (nonatomic, readonly) MYPackageProfileType type; 43 | @property (nonatomic, readonly) NSString *typeName; 44 | 45 | - (BOOL)isMatchAppBundleID:(NSString *)bundleId; 46 | 47 | + (NSArray *)arrayWithJSONObject:(NSArray *)json; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /Core/Model/MYPackageProfile.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageProfile.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/2/8. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageProfile.h" 10 | 11 | NSString *profileTypeNameForType(MYPackageProfileType type) { 12 | switch (type) { 13 | case MYPackageProfileTypeEnterprise: 14 | return @"enterprise"; 15 | case MYPackageProfileTypeAdhoc: 16 | return @"ad-hoc"; 17 | case MYPackageProfileTypeAppStore: 18 | return @"app-store"; 19 | default: 20 | return @"development"; 21 | } 22 | } 23 | 24 | @implementation MYPackageProfile 25 | @synthesize ApplicationIdentifier = _ApplicationIdentifier; 26 | 27 | - (NSString *)ApplicationIdentifier { 28 | if (_ApplicationIdentifier) { 29 | return _ApplicationIdentifier; 30 | } 31 | NSString *identifier = self.Entitlements[@"application-identifier"]; 32 | for (NSString *prefix in self.ApplicationIdentifierPrefix) { 33 | if ([identifier hasPrefix:prefix]) { 34 | identifier = [identifier substringFromIndex:[prefix length] + 1]; 35 | break; 36 | } 37 | } 38 | _ApplicationIdentifier = identifier; 39 | return _ApplicationIdentifier; 40 | } 41 | 42 | - (void)setValue:(id)value forUndefinedKey:(NSString *)key { 43 | 44 | } 45 | 46 | - (BOOL)isMatchAppBundleID:(NSString *)bundleId { 47 | NSString *expression = [[self.ApplicationIdentifier stringByReplacingOccurrencesOfString:@"." withString:@"\\."] stringByReplacingOccurrencesOfString:@"*" withString:@".*"]; 48 | NSError *error = nil; 49 | 50 | NSRegularExpression *regExpr = 51 | [NSRegularExpression regularExpressionWithPattern:expression 52 | options:NSRegularExpressionCaseInsensitive 53 | error:&error]; 54 | 55 | NSTextCheckingResult *matchResult = [regExpr firstMatchInString:bundleId 56 | options:0 57 | range:NSMakeRange(0, [bundleId length])]; 58 | return matchResult ? YES : NO; 59 | } 60 | 61 | - (NSString *)SignCertificate { 62 | for (NSString *uuid in [self DeveloperCertificates]) { 63 | if (![uuid isKindOfClass:[NSNull class]]) { 64 | return uuid; 65 | } 66 | } 67 | return nil; 68 | } 69 | 70 | - (MYPackageProfileType)type { 71 | if ([self.Entitlements[@"get-task-allow"] boolValue]) { 72 | return MYPackageProfileTypeDevelopment; 73 | } 74 | if ([self.Entitlements[@"beta-reports-active"] boolValue]) { 75 | return MYPackageProfileTypeAppStore; 76 | } 77 | return MYPackageProfileTypeAdhoc; 78 | } 79 | 80 | - (NSString *)typeName { 81 | return profileTypeNameForType(self.type); 82 | } 83 | 84 | - (NSString *)description { 85 | if ([self.ApplicationIdentifier isEqualToString:@"*"]) { 86 | return [NSString stringWithFormat:@"%@ (%@)", self.Name, self.TeamName]; 87 | } 88 | return self.Name; 89 | } 90 | 91 | + (NSArray *)arrayWithJSONObject:(NSArray *)json { 92 | NSMutableArray *array = [NSMutableArray arrayWithCapacity:[json count]]; 93 | [json enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL * _Nonnull stop) { 94 | MYPackageProfile *profile = [[MYPackageProfile alloc] init]; 95 | [dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { 96 | [profile setValue:obj forKey:key]; 97 | }]; 98 | [array addObject:profile]; 99 | }]; 100 | return array; 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /Core/Model/MYPackageProject.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageProject.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/7. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageTarget.h" 10 | #import "MYPackageScheme.h" 11 | 12 | @interface MYPackageProject : NSObject 13 | 14 | @property (nonatomic, strong) NSString *name; 15 | 16 | @property (nonatomic, strong) NSString *filePath; 17 | 18 | @property (nonatomic, strong) NSArray *targetNames; 19 | 20 | @property (nonatomic, strong) NSMutableDictionary *targets; 21 | 22 | @property (nonatomic, strong) NSArray *configrations; 23 | 24 | /// 项目文件信息 25 | @property (nonatomic, strong) NSDictionary *info; 26 | 27 | - (MYPackageTarget *)targetForName:(NSString *)name; 28 | - (NSArray *)targetsForTargetNames:(NSArray *)names; 29 | 30 | - (MYPackageTarget *)demoTargetForName:(NSString *)name; 31 | - (NSArray *)demoTargetsForTargetNames:(NSArray *)names; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Core/Model/MYPackageProject.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageProject.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/7. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageProject.h" 10 | 11 | @interface MYPackageTarget () 12 | 13 | @end 14 | 15 | @implementation MYPackageProject 16 | 17 | - (NSMutableDictionary *)targets { 18 | if (!_targets) { 19 | _targets = [[NSMutableDictionary alloc] init]; 20 | } 21 | return _targets; 22 | } 23 | 24 | - (MYPackageTarget *)targetForName:(NSString *)name { 25 | return [self.targets objectForKey:name]; 26 | } 27 | 28 | - (NSArray *)targetsForTargetNames:(NSArray *)names { 29 | NSMutableArray *targets = [NSMutableArray array]; 30 | for (NSString *name in names) { 31 | MYPackageTarget *target = [self targetForName:name]; 32 | if (target) { 33 | [targets addObject:target]; 34 | } 35 | } 36 | return targets; 37 | } 38 | 39 | - (MYPackageTarget *)demoTargetForName:(NSString *)name { 40 | return [self targetForName:[NSString stringWithFormat:@"%@Demo", name]]; 41 | } 42 | 43 | - (NSArray *)demoTargetsForTargetNames:(NSArray *)names { 44 | NSMutableArray *demoTargets = [NSMutableArray array]; 45 | for (NSString *name in names) { 46 | MYPackageTarget *demoTarget = [self demoTargetForName:name]; 47 | if (demoTarget) { 48 | [demoTargets addObject:demoTarget]; 49 | } 50 | } 51 | return demoTargets; 52 | } 53 | 54 | - (void)setInfo:(NSDictionary *)info { 55 | _info = info; 56 | for (MYPackageTarget *target in [self.targets allValues]) { 57 | target.userConfigrations = [self valueForKey:target.name inArray:self.info[@"Targets"]]; 58 | } 59 | } 60 | 61 | - (id)valueForKey:(NSString *)key inArray:(NSArray *)array { 62 | for (NSDictionary *dic in array) { 63 | NSArray *allKeys = [dic allKeys]; 64 | if ([allKeys count] > 0) { 65 | if ([allKeys[0] isEqualToString:key]) { 66 | return dic[key]; 67 | } 68 | } 69 | } 70 | return nil; 71 | } 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /Core/Model/MYPackageScheme.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageScheme.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/20. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MYPackageTarget.h" 11 | 12 | @class MYPackageWorkspace; 13 | @interface MYPackageScheme : NSObject 14 | 15 | @property (nonatomic, weak) MYPackageWorkspace *workspace; 16 | 17 | @property (nonatomic, strong) NSString *name; 18 | 19 | @property (nonatomic, strong) NSString *filePath; 20 | @property (nonatomic, strong) NSString *container; 21 | 22 | //! targetName->projectPath 23 | @property (nonatomic, strong) NSDictionary *targetNames; 24 | 25 | @property (nonatomic, strong, readonly) NSArray *targets; 26 | @property (nonatomic, strong, readonly) NSArray *libraryTargets; 27 | @property (nonatomic, strong, readonly) NSArray *projects; 28 | 29 | - (NSArray *)targetsForType:(MYPackageTargetType)type; 30 | @end 31 | -------------------------------------------------------------------------------- /Core/Model/MYPackageScheme.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageScheme.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/20. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageScheme.h" 10 | #import "MYPackageWorkspace.h" 11 | 12 | @implementation MYPackageScheme 13 | 14 | @synthesize targets = _targets; 15 | @synthesize projects = _projects; 16 | @synthesize libraryTargets = _libraryTargets; 17 | 18 | - (void)setFilePath:(NSString *)filePath { 19 | _filePath = filePath; 20 | self.name = [[filePath lastPathComponent] stringByDeletingPathExtension]; 21 | } 22 | 23 | - (void)setTargetNames:(NSDictionary *)targetNames { 24 | _targetNames = targetNames; 25 | _targets = nil; 26 | _projects = nil; 27 | } 28 | 29 | - (NSArray *)targets { 30 | if (_targets) { 31 | return _targets; 32 | } 33 | if ([self.targetNames count] == 0) { 34 | return nil; 35 | } 36 | if (!_targets) { 37 | _targets = [self.workspace targetsWithDictionary:self.targetNames]; 38 | } 39 | return _targets; 40 | } 41 | 42 | - (NSArray *)libraryTargets { 43 | if (!_libraryTargets) { 44 | _libraryTargets = [self targetsForType:MYPackageTargetTypeStaticLibrary|MYPackageTargetTypeObjectFile|MYPackageTargetTypeDynamicLibrary]; 45 | } 46 | return _libraryTargets; 47 | } 48 | 49 | - (NSArray *)projects { 50 | if (!_projects) { 51 | _projects = [self.workspace projectsWithPaths:[_targetNames allValues]]; 52 | } 53 | return _projects; 54 | } 55 | 56 | - (NSArray *)targetsForType:(MYPackageTargetType)type { 57 | return [self.targets filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL (MYPackageTarget *_Nonnull target, NSDictionary < NSString *, id > *_Nullable bindings) { 58 | return (target.type & type) > 0; 59 | }]]; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /Core/Model/MYPackageTarget.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageTarget.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/7. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | #import "MYPackageTargetDefine.h" 11 | 12 | @class MYPackageProject; 13 | 14 | @interface MYPackageTarget : NSObject 15 | 16 | @property (nonatomic, weak) MYPackageProject *project; 17 | 18 | @property (nonatomic, copy) NSString *name; 19 | 20 | /// 项目中的配置,存在配置变量 21 | @property (nonatomic, strong) NSDictionary *userConfigrations; 22 | @property (nonatomic, strong) NSDictionary *userBuildSettings; 23 | 24 | 25 | @property (nonatomic, strong) NSArray *libraries; 26 | @property (nonatomic, strong) NSArray *frameworks; 27 | 28 | // 本地链接的 framework,因为 XCode 不会将 framework link 到静态库中 29 | @property (nonatomic, strong) NSDictionary *localFrameworks; 30 | 31 | /// 输出的最终 Configurations,变量被解析了 32 | @property (nonatomic, strong) NSDictionary *configurations; 33 | 34 | @property (nonatomic, readonly) NSArray *resources; 35 | 36 | @property (nonatomic, readonly) NSString *bundleId; 37 | @property (nonatomic, readonly) NSString *wrapperExtension; 38 | @property (nonatomic, readonly) NSString *productName; 39 | @property (nonatomic, readonly) NSString *fullProductName; 40 | @property (nonatomic, readonly) NSString *binaryPath; 41 | @property (nonatomic, readonly) NSString *originInfoPlistPath; 42 | @property (nonatomic, readonly) NSString *infoPlistPath; 43 | 44 | @property (nonatomic, strong) NSString *resourcePath; 45 | @property (nonatomic, readonly) NSString *publicHeaderPath; 46 | 47 | @property (nonatomic, readonly) NSString *sdkName; 48 | @property (nonatomic, readonly) MYPackageTargetPlatformType sdk; 49 | @property (nonatomic, readonly) MYPackageTargetPlatformSubType sdkEnv; 50 | 51 | @property (nonatomic, readonly) BOOL needLipo; 52 | 53 | @property (nonatomic, readonly) NSString *platformName; 54 | @property (nonatomic, readonly) NSString *platformMinVersion; 55 | 56 | @property (nonatomic, readonly) MYPackageTargetType type; 57 | - (BOOL)isSharedLibrary; 58 | 59 | @property (nonatomic, readonly) NSString *zipFileName; 60 | 61 | - (id)initWithName:(NSString *)name; 62 | - (NSString *)configurationForKey:(NSString *)key; 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /Core/Model/MYPackageTargetDefine.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageTargetDefine.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/5/15. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #ifndef MYPackageTargetDefine_h 10 | #define MYPackageTargetDefine_h 11 | 12 | typedef NS_OPTIONS (NSInteger, MYPackageTargetType) { 13 | MYPackageTargetTypeUnknown = 0, 14 | MYPackageTargetTypeStaticLibrary = 1, 15 | MYPackageTargetTypeExecutable = 1 << 1, 16 | MYPackageTargetTypeDynamicLibrary = 1 << 2, 17 | MYPackageTargetTypeBundle = 1 << 3, 18 | MYPackageTargetTypeObjectFile = 1 << 4 19 | }; 20 | 21 | typedef NS_ENUM (NSInteger, MYPackageTargetPlatformType) { 22 | MYPackageTargetPlatformType_iOS = 1 << 0, 23 | MYPackageTargetPlatformType_macOS = 1 << 1, 24 | MYPackageTargetPlatformType_watchOS = 1 << 2, 25 | MYPackageTargetPlatformType_tvOS = 1 << 3 26 | }; 27 | 28 | typedef NS_OPTIONS (NSInteger, MYPackageTargetPlatformSubType) { 29 | MYPackageTargetPlatformSubTypeDevice = 1 << 0, 30 | MYPackageTargetPlatformSubTypeSimulator = 1 << 1, 31 | MYPackageTargetPlatformSubTypeBoth = MYPackageTargetPlatformSubTypeDevice | MYPackageTargetPlatformSubTypeSimulator 32 | }; 33 | 34 | MYPackageTargetPlatformType SDKTypeForName(NSString *platform); 35 | NSString *SDKNameForType(MYPackageTargetPlatformType type); 36 | 37 | MYPackageTargetPlatformType platformTypeForName(NSString *platform); 38 | NSString *platformNameForType(MYPackageTargetPlatformType type); 39 | 40 | 41 | #endif /* MYPackageTargetDefine_h */ 42 | -------------------------------------------------------------------------------- /Core/Model/MYPackageTargetDefine.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageTargetDefine.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/5/15. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MYPackageTargetDefine.h" 11 | 12 | MYPackageTargetPlatformType SDKTypeForName(NSString *sdk) { 13 | if ([sdk rangeOfString:@"macosx"].location != NSNotFound) { 14 | return MYPackageTargetPlatformType_macOS; 15 | } 16 | if ([sdk rangeOfString:@"iphone"].location != NSNotFound) { 17 | return MYPackageTargetPlatformType_iOS; 18 | } 19 | if ([sdk rangeOfString:@"appletv"].location != NSNotFound) { 20 | return MYPackageTargetPlatformType_tvOS; 21 | } 22 | if ([sdk rangeOfString:@"watch"].location != NSNotFound) { 23 | return MYPackageTargetPlatformType_watchOS; 24 | } 25 | return MYPackageTargetPlatformType_iOS; 26 | } 27 | 28 | NSString *SDKNameForType(MYPackageTargetPlatformType type) { 29 | switch (type) { 30 | case MYPackageTargetPlatformType_iOS: 31 | return @"iphone"; 32 | case MYPackageTargetPlatformType_macOS: 33 | return @"macosx"; 34 | case MYPackageTargetPlatformType_tvOS: 35 | return @"appletv"; 36 | case MYPackageTargetPlatformType_watchOS: 37 | return @"watch"; 38 | default: 39 | return @"iphone"; 40 | } 41 | } 42 | 43 | MYPackageTargetPlatformType platformTypeForName(NSString *platform) { 44 | if ([platform rangeOfString:@"macos"].location != NSNotFound) { 45 | return MYPackageTargetPlatformType_macOS; 46 | } 47 | if ([platform rangeOfString:@"ios"].location != NSNotFound) { 48 | return MYPackageTargetPlatformType_iOS; 49 | } 50 | if ([platform rangeOfString:@"tvos"].location != NSNotFound) { 51 | return MYPackageTargetPlatformType_tvOS; 52 | } 53 | if ([platform rangeOfString:@"watchos"].location != NSNotFound) { 54 | return MYPackageTargetPlatformType_watchOS; 55 | } 56 | return MYPackageTargetPlatformType_iOS; 57 | } 58 | 59 | NSString *platformNameForType(MYPackageTargetPlatformType type) { 60 | switch (type) { 61 | case MYPackageTargetPlatformType_iOS: 62 | return @"ios"; 63 | case MYPackageTargetPlatformType_macOS: 64 | return @"macos"; 65 | case MYPackageTargetPlatformType_tvOS: 66 | return @"tvos"; 67 | case MYPackageTargetPlatformType_watchOS: 68 | return @"watchos"; 69 | default: 70 | return @"ios"; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Core/Model/MYPackageWorkspace.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageWorkspace.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/3/15. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MYPackageProject.h" 11 | 12 | @interface MYPackageWorkspace : NSObject 13 | 14 | @property (nonatomic, readonly) NSString *name; 15 | @property (nonatomic, strong) NSString *path; // if not set, use filePath dirname 16 | @property (nonatomic, strong) NSString *filePath; 17 | 18 | @property (nonatomic, strong) NSMutableDictionary *projects; 19 | 20 | @property (nonatomic, strong) NSArray *projectPaths; 21 | 22 | @property (nonatomic, strong) NSArray *schemes; 23 | 24 | /*! 根据字典信息获取 targets 25 | * 26 | * dict 的 key 是 target 名称,value 是 target 所在项目的路径 27 | */ 28 | - (NSArray *)targetsWithDictionary:(NSDictionary *)dict; 29 | - (NSArray *)demoTargetsWithDictionary:(NSDictionary *)dict; 30 | 31 | - (NSArray *)projectsWithPaths:(NSArray *)paths; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Core/Model/MYPackageWorkspace.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageWorkspace.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/3/15. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageWorkspace.h" 10 | #import "MYPackageWorkspaceParser.h" 11 | 12 | @implementation MYPackageWorkspace 13 | 14 | - (void)setFilePath:(NSString *)filePath { 15 | _filePath = filePath; 16 | _name = [[filePath lastPathComponent] stringByDeletingPathExtension]; 17 | self.path = [filePath stringByDeletingLastPathComponent]; 18 | if (PathIsProject(filePath)) { 19 | self.projectPaths = @[filePath]; 20 | } else if (PathIsWorkspace(filePath)) { 21 | NSArray *projects = [MYPackageWorkspaceParser projectsInWorkspace:filePath]; 22 | NSMutableArray *paths = [NSMutableArray arrayWithCapacity:projects.count]; 23 | [projects enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { 24 | [paths addObject:[[self.path stringByAppendingPathComponent:obj] stringByStandardizingPath]]; 25 | }]; 26 | self.projectPaths = paths; 27 | } 28 | } 29 | 30 | - (void)setSchemes:(NSArray *)schemes { 31 | _schemes = schemes; 32 | [schemes setValue:self forKeyPath:@"workspace"]; 33 | } 34 | 35 | - (NSMutableDictionary *)projects { 36 | if (!_projects) { 37 | _projects = [[NSMutableDictionary alloc] init]; 38 | } 39 | return _projects; 40 | } 41 | 42 | #pragma mark - targets 43 | - (NSArray *)targetsWithDictionary:(NSDictionary *)dict { 44 | NSMutableArray *targets = [NSMutableArray arrayWithCapacity:dict.count]; 45 | [dict enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull name, NSString *_Nonnull path, BOOL *_Nonnull stop) { 46 | MYPackageProject *project = [self.projects objectForKey:path]; 47 | MYPackageTarget *target = [project targetForName:name]; 48 | if (target) { 49 | [targets addObject:target]; 50 | } 51 | }]; 52 | return [targets count] > 0 ? targets : nil; 53 | } 54 | 55 | - (NSArray *)demoTargetsWithDictionary:(NSDictionary *)dict { 56 | NSMutableArray *targets = [NSMutableArray arrayWithCapacity:dict.count]; 57 | [dict enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull name, NSString *_Nonnull path, BOOL *_Nonnull stop) { 58 | MYPackageProject *project = [self.projects objectForKey:path]; 59 | MYPackageTarget *target = [project demoTargetForName:name]; 60 | if (target) { 61 | [targets addObject:target]; 62 | } 63 | }]; 64 | return targets; 65 | } 66 | 67 | #pragma mark - projects 68 | - (NSArray *)projectsWithPaths:(NSArray *)paths { 69 | NSArray *p = [paths valueForKeyPath:@"@distinctUnionOfObjects.self"]; 70 | NSMutableArray *array = [NSMutableArray arrayWithCapacity:[p count]]; 71 | [p enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { 72 | MYPackageProject *project = [self.projects objectForKey:obj]; 73 | if (project) { 74 | [array addObject:project]; 75 | } 76 | }]; 77 | return array; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /Core/Task/Clean/MYPackageCleanFinalProductTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCleanFinalProductTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/8/9. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBaseTask.h" 10 | 11 | @interface MYPackageCleanFinalProductTask : MYPackageBaseTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Clean/MYPackageCleanFinalProductTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCleanFinalProductTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/8/9. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageCleanFinalProductTask.h" 10 | 11 | @implementation MYPackageCleanFinalProductTask 12 | 13 | - (BOOL)launch { 14 | if (![super launch]) { 15 | return NO; 16 | } 17 | NSError *error; 18 | for (NSString *path in @[self.config.logPath, self.config.productsDir]) { 19 | if (![[NSFileManager defaultManager] removeItemAtPath:path 20 | error:&error]) { 21 | self.errorMessage = [NSString stringWithFormat:@"delete failed: %@ %@", path, [error description]]; 22 | return NO; 23 | } 24 | } 25 | return YES; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Core/Task/Clean/MYPackageCleanIntermediateProductTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCleanIntermediateProductTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/8/9. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBaseTask.h" 10 | 11 | @interface MYPackageCleanIntermediateProductTask : MYPackageBaseTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Clean/MYPackageCleanIntermediateProductTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCleanIntermediateProductTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/8/9. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageCleanIntermediateProductTask.h" 10 | 11 | @implementation MYPackageCleanIntermediateProductTask 12 | 13 | - (NSString *)name { 14 | return @"清理中间产物"; 15 | } 16 | 17 | - (BOOL)launch { 18 | if (![super launch]) { 19 | return NO; 20 | } 21 | NSError *error; 22 | NSFileManager *fileManger = [NSFileManager defaultManager]; 23 | NSArray *files = [fileManger contentsOfDirectoryAtPath:self.config.outputDir error:&error]; 24 | if (files == nil) { 25 | self.errorMessage = [NSString stringWithFormat:@"List file failed: %@ %@", self.config.outputDir, error]; 26 | } 27 | NSString *path; 28 | for (NSString *filename in files) { 29 | if (![filename isEqualToString:@"products"] && ![[filename stringByDeletingPathExtension] isEqualToString:@"log"]) { 30 | path = [self.config.outputDir stringByAppendingPathComponent:filename]; 31 | if (![fileManger removeItemAtPath:path error:&error]) { 32 | self.errorMessage = [NSString stringWithFormat:@"delete failed: %@ %@", path, [error description]]; 33 | return NO; 34 | } else { 35 | [self.config.logger logN:@"delete %@", path]; 36 | } 37 | } 38 | } 39 | return YES; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Core/Task/Compile/MYPackageBuildAppTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageBuildAppTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/2/6. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageBuildAppTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Compile/MYPackageBuildAppTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageBuildAppTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/2/6. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBuildAppTask.h" 10 | 11 | @implementation MYPackageBuildAppTask 12 | 13 | - (NSString *)name { 14 | return @"编译二进制"; 15 | } 16 | 17 | - (BOOL)launch { 18 | if (![super launch]) { 19 | return NO; 20 | } 21 | 22 | [[NSFileManager defaultManager] createDirectoryAtPath:self.config.productsDir withIntermediateDirectories:YES attributes:nil error:nil]; 23 | 24 | NSString *archivePath = [self.config.productsDir stringByAppendingPathComponent:@"archive"]; 25 | 26 | if ([self executeCommand:[NSString stringWithFormat:@"set -o pipefail && xcodebuild %@ \"%@\" -configuration \"%@\" -hideShellScriptEnvironment -xcconfig \"%@\" %@ -scheme \"%@\" archive -archivePath \"%@\" | bundle exec xcpretty", 27 | PathIsProject(self.config.workspaceFilePath) ? @"-project" : @"-workspace", 28 | self.config.workspaceFilePath, 29 | self.config.configruation, 30 | self.config.xcconfig, 31 | self.config.xcconfigSettings ?: @"", 32 | self.config.selectedSchemeName, 33 | archivePath 34 | ]] != 0) { 35 | self.errorMessage = @"编译项目出错,详见日志!"; 36 | return NO; 37 | } 38 | return YES; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Core/Task/Compile/MYPackageBuildLibTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageBuildLibTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/4. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageBuildLibTask : MYPackageShellTask 12 | @end 13 | -------------------------------------------------------------------------------- /Core/Task/Compile/MYPackageBuildLibTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageBuildLibTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/4. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBuildLibTask.h" 10 | 11 | @interface MYPackageBuildLibTask () 12 | @end 13 | 14 | @implementation MYPackageBuildLibTask 15 | 16 | - (NSString *)name { 17 | return @"编译二进制"; 18 | } 19 | 20 | - (BOOL)launch { 21 | if (![super launch]) { 22 | return NO; 23 | } 24 | NSMutableArray *simulators = [NSMutableArray array]; 25 | NSMutableArray *devices = [NSMutableArray array]; 26 | for (MYPackageTarget *target in self.config.selectedScheme.targets) { 27 | if (target.needLipo) { 28 | [simulators addObject:[NSString stringWithFormat:@"-destination 'generic/platform=%@ Simulator'", target.platformName]]; 29 | } 30 | [devices addObject:[NSString stringWithFormat:@"-destination 'generic/platform=%@'", target.platformName]]; 31 | } 32 | simulators = [simulators valueForKeyPath:@"@distinctUnionOfObjects.self"]; 33 | devices = [devices valueForKeyPath:@"@distinctUnionOfObjects.self"]; 34 | NSString *cmd = [NSString stringWithFormat:@"xcodebuild %@ '%@' -configuration '%@' -xcconfig '%@' -scheme '%@' %@", 35 | PathIsWorkspace(self.config.workspace.filePath) ? @"-workspace" : @"-project", 36 | self.config.workspace.filePath, 37 | self.config.configruation, 38 | self.config.xcconfig, 39 | self.config.selectedScheme.name, 40 | self.config.xcconfigSettings ?: @""]; 41 | if ([simulators count] > 0 && 42 | [self executeCommand:[NSString stringWithFormat:@"%@ CONFIGURATION_BUILD_DIR='build/simulator/$(PLATFORM_NAME)/' %@ build", 43 | cmd, 44 | [simulators componentsJoinedByString:@" "]]] != 0) { 45 | self.errorMessage = @"编译项目出错,详见日志!"; 46 | return NO; 47 | } 48 | if ([devices count] > 0 && 49 | [self executeCommand:[NSString stringWithFormat:@"%@ INSTALL_PATH='build/os/$(PLATFORM_NAME)/' INSTALL_ROOT='' SKIP_INSTALL=NO DWARF_DSYM_FOLDER_PATH='$(pwd)/build/iphoneos' %@ archive", cmd, 50 | [devices componentsJoinedByString:@" "]]] != 0) { 51 | self.errorMessage = @"编译项目出错,详见日志!"; 52 | return NO; 53 | } 54 | return YES; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /Core/Task/Compile/MYPackageCleanTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCleanTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/5. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageCleanTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Compile/MYPackageCleanTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCleanTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/5. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageCleanTask.h" 10 | 11 | @implementation MYPackageCleanTask 12 | 13 | - (NSString *)name { 14 | return @"清理编译环境"; 15 | } 16 | 17 | - (BOOL)launch { 18 | if (![super launch]) { 19 | return NO; 20 | } 21 | NSString *cmd = [NSString stringWithFormat:@"set -o pipefail && xcodebuild clean %@ '%@' -scheme '%@' -configuration '%@' | bundle exec xcpretty", PathIsWorkspace(self.config.workspace.filePath) ? @"-workspace" : @"-project", self.config.workspace.filePath, self.config.selectedSchemeName, self.config.configruation]; 22 | if ([self executeCommand:cmd] != 0) { 23 | self.errorMessage = @"执行清理出错!"; 24 | return NO; 25 | } 26 | NSError *error; 27 | NSFileManager *fileManger = [NSFileManager defaultManager]; 28 | NSArray *files = [fileManger contentsOfDirectoryAtPath:self.config.outputDir error:&error]; 29 | if (files == nil) { 30 | self.errorMessage = [NSString stringWithFormat:@"List file failed: %@ %@", self.config.outputDir, error]; 31 | } 32 | NSString *path; 33 | for (NSString *filename in files) { 34 | if (![[filename stringByDeletingPathExtension] isEqualToString:@"log"]) { 35 | path = [self.config.outputDir stringByAppendingPathComponent:filename]; 36 | if (![fileManger removeItemAtPath:path error:&error]) { 37 | self.errorMessage = [NSString stringWithFormat:@"delete failed: %@ %@", path, [error description]]; 38 | return NO; 39 | } else { 40 | [self.config.logger logN:@"delete %@", path]; 41 | } 42 | } 43 | } 44 | return YES; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Core/Task/Compile/MYPackageLipoTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageLipoTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/3/14. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageLipoTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Compile/MYPackageLipoTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageLipoTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/3/14. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageLipoTask.h" 10 | 11 | @implementation MYPackageLipoTask 12 | 13 | - (NSString *)name { 14 | return @"合并通用二进制"; 15 | } 16 | 17 | - (BOOL)launch { 18 | if (![super launch]) { 19 | return NO; 20 | } 21 | __block NSError *error = nil; 22 | NSFileManager *fm = [NSFileManager defaultManager]; 23 | [fm removeItemAtPath:self.config.productsDir 24 | error:nil]; 25 | [fm createDirectoryAtPath:self.config.productsDir 26 | withIntermediateDirectories:YES 27 | attributes:nil 28 | error:nil]; 29 | 30 | [[self.config.selectedScheme targets] enumerateObjectsUsingBlock:^(MYPackageTarget *_Nonnull target, NSUInteger idx, BOOL *_Nonnull stop) { 31 | NSString *fromPath = [[self.config devicePathForTarget:target] stringByAppendingPathComponent:target.fullProductName]; 32 | NSString *toPath = [[self.config productPathForTarget:target] stringByAppendingPathComponent:target.fullProductName]; 33 | if ((target.sdkEnv & MYPackageTargetPlatformSubTypeDevice) == 0) { 34 | fromPath = [[self.config simulatorPathForTarget:target] stringByAppendingPathComponent:target.fullProductName]; 35 | } 36 | [self moveResourceFilesToFolder:target productPath:fromPath]; 37 | if (!target.needLipo) { 38 | [self.config.logger logN:@"cp %@ %@", [fromPath substringFromIndex:self.config.outputDir.length + 1], [toPath substringFromIndex:self.config.outputDir.length + 1]]; 39 | if (![fm copyItemAtPath:fromPath 40 | toPath:toPath 41 | error:&error]) { 42 | self.errorMessage = [error localizedDescription]; 43 | *stop = YES; 44 | } 45 | } 46 | }]; 47 | 48 | if (self.errorMessage) { 49 | return NO; 50 | } 51 | self.workingDirectory = self.config.outputDir; 52 | NSString *shellPath = [self scriptForName:@"lipo" ofType:@"sh"]; 53 | if ([self executeCommand:[NSString stringWithFormat:@"\"%@\"", shellPath]] != 0) { 54 | self.errorMessage = @"合并通用二进制出错,详见日志!"; 55 | return NO; 56 | } 57 | return YES; 58 | } 59 | 60 | - (void)moveResourceFilesToFolder:(MYPackageTarget *)target productPath:(NSString *)productPath { 61 | if (target.type != MYPackageTargetTypeStaticLibrary) { 62 | // 只有静态库需要移动资源到 Resources 目录 63 | return; 64 | } 65 | if (![target.wrapperExtension isEqualToString:@"framework"]) { 66 | // 只有 Framework 才有 Resources 目录 67 | return; 68 | } 69 | NSFileManager *fm = [NSFileManager defaultManager]; 70 | NSString *resourcePath = [productPath stringByAppendingPathComponent:@"Resources"]; 71 | if ([[target.resourcePath lastPathComponent] isEqualToString:@"Resources"] || [fm fileExistsAtPath:resourcePath]) { 72 | return; 73 | } 74 | if ([target.resources count] == 0) { 75 | return; 76 | } 77 | 78 | [fm createDirectoryAtPath:resourcePath 79 | withIntermediateDirectories:YES 80 | attributes:nil 81 | error:nil]; 82 | 83 | NSString *oldResourcePath = [productPath stringByAppendingPathComponent:[target.resourcePath substringFromIndex:target.fullProductName.length + 1]]; 84 | NSDictionary *compileMapper = @{@"storyboard": @"storyboardc", 85 | @"xib": @"nib", 86 | @"xcdatamodel": @"mom", 87 | @"xcdatamodeld": @"momd", 88 | @"xcmappingmodel": @"cdm", 89 | @"xcasset": @"car"}; 90 | for (NSString *fileName in target.resources) { 91 | NSString *outFileName = fileName; 92 | 93 | // 部分资源编译后后缀发生变化 94 | NSString *extName = [compileMapper objectForKey:[fileName pathExtension]]; 95 | if (extName) { 96 | outFileName = [[outFileName stringByDeletingPathExtension] stringByAppendingPathExtension:extName]; 97 | } 98 | 99 | NSString *oldPath = [oldResourcePath stringByAppendingPathComponent:outFileName]; 100 | if ([fm fileExistsAtPath:oldPath]) { 101 | [fm moveItemAtPath:oldPath 102 | toPath:[resourcePath stringByAppendingPathComponent:outFileName] 103 | error:nil]; 104 | } 105 | } 106 | 107 | // 更新 target 的资源路径 108 | target.resourcePath = [target.fullProductName stringByAppendingPathComponent:@"Resources"]; 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /Core/Task/Compile/Shell/build.xcconfig: -------------------------------------------------------------------------------- 1 | 2 | CODE_SIGNING_REQUIRED = NO 3 | CODE_SIGNING_ALLOWED = NO 4 | CODE_SIGN_IDENTITY = 5 | EXPANDED_CODE_SIGN_IDENTITY = 6 | -------------------------------------------------------------------------------- /Core/Task/Compile/Shell/create-scheme.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | require 'pathname' 3 | selected_scheme_path= '/Users/Whirlwind/Documents/Developer/TestPackage/TestPackage.xcodeproj/xcshareddata/xcschemes/TestPackage.xcscheme' 4 | selected_project_path = '/Users/Whirlwind/Documents/Developer/TestPackage/TestPackage.xcodeproj' 5 | selected_scheme_container = Pathname.new(selected_project_path).dirname.to_s 6 | #selected_scheme_container = '/Users/Whirlwind/Documents/Developer/XcodePackage' 7 | #selected_scheme_path = '/Users/Whirlwind/Documents/Developer/XcodePackage/Package.xcworkspace/xcuserdata/whirlwind.xcuserdatad/xcschemes/Package1.xcscheme' 8 | 9 | require 'xcodeproj' 10 | require 'fileutils' 11 | 12 | prefix = "container:" 13 | 14 | selected_scheme = Xcodeproj::XCScheme.new(selected_scheme_path) 15 | 16 | puts selected_scheme.build_action.entries 17 | 18 | selected_target_references = selected_scheme.build_action.entries.map {|entry| entry.buildable_references}.flatten 19 | 20 | selected_scheme_content = {} 21 | selected_target_references.each do |ref| 22 | selected_scheme_content[ref.target_name] = ref.target_referenced_container[prefix.length..-1] 23 | end 24 | 25 | projects = {} 26 | selected_scheme_content.values.uniq.each do |project_path| 27 | path = File.join(selected_scheme_container, project_path) 28 | projects[project_path] = Xcodeproj::Project.open(path) 29 | end 30 | 31 | 32 | TARGETS = {} 33 | 34 | def add_target(target) 35 | platform = target.common_resolved_build_setting("SUPPORTED_PLATFORMS") || target.sdk 36 | sdks = [] 37 | sdks << "ios" if platform.index 'iphoneos' 38 | sdks << "osx" if platform.index 'macosx' 39 | sdks << "tvos" if platform.index 'appletvos' 40 | sdks << "watchos" if platform.index 'watchos' 41 | 42 | type = target.common_resolved_build_setting("MACH_O_TYPE") 43 | if type.nil? 44 | type = case target.symbol_type 45 | when :framework 46 | "mh_dylib" 47 | when :dynamic_library 48 | "mh_dylib" 49 | when :static_library 50 | "staticlib" 51 | when :bundle 52 | "mh_bundle" 53 | else 54 | "mh_execute" 55 | end 56 | end 57 | 58 | TARGETS[target] = {:sdk => sdks, :type => type} 59 | end 60 | 61 | selected_scheme_content.each do |target_name, project_path| 62 | project = projects[project_path] 63 | target = project.targets.find {|t| t.name == target_name} 64 | puts "--------" 65 | puts target.name 66 | puts "resources:", target.resources_build_phase.file_display_names.inspect 67 | # add_target(target) 68 | end 69 | 70 | user_data_dir = Xcodeproj::XCScheme.user_data_dir(selected_project_path, "XcodePackage") 71 | FileUtils.rm_rf(user_data_dir) 72 | 73 | #ENV['USER'] = 'XcodePackage' 74 | # 75 | #schemes = {} 76 | #TARGETS.each do |target, value| 77 | # type = value[:type] 78 | # value[:sdk].each do |sdk| 79 | # scheme_name = "#{sdk}-#{type}" 80 | # scheme = schemes[scheme_name] ||= Xcodeproj::XCScheme.new() 81 | # scheme.add_build_target(target, false) 82 | # end 83 | #end 84 | #schemes.each do |name, scheme| 85 | # scheme.save_as(selected_project_path, name, false) 86 | #end 87 | -------------------------------------------------------------------------------- /Core/Task/Compile/Shell/lipo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # lipo.sh 4 | # Package 5 | # 6 | # Created by Whirlwind on 16/3/14. 7 | # Copyright © 2016年 taobao. All rights reserved. 8 | # 9 | # Usage: 10 | # ./lipo.sh 11 | # 12 | 13 | iphoneos="iphoneos" 14 | iphonesimulator="iphonesimulator" 15 | 16 | for os in os/*; do 17 | if ! [[ -d "$os" ]]; then 18 | continue 19 | fi 20 | echo $os 21 | dirname=$(basename $os) 22 | device="${dirname%*os}" 23 | simulator="simulator/${device}simulator" 24 | output="products/${device}" 25 | mkdir -p "$output/" 26 | for f in ${os}/*; do 27 | name=$(basename $f) 28 | product="$output/$name" 29 | if ! [[ -e "$simulator/$name" ]]; then 30 | continue 31 | fi 32 | if [[ -d "$f" ]]; then 33 | # 文件夹,例如 .framework 34 | filename="${name%.*}" 35 | if ! [[ -d "$product" ]]; then 36 | cp -r "$f" "$product" 37 | fi 38 | set -x && lipo -create "$f/$filename" "$simulator/$name/$filename" -output "$product/$filename" 39 | elif [[ -f "$f" ]]; then 40 | # 文件,例如 .a 41 | set -x && lipo -create "$f" "$simulator/$name" -output "$product" 42 | fi 43 | done 44 | done 45 | -------------------------------------------------------------------------------- /Core/Task/MYPackageBaseTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageBaseTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/4. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MYPackageConfig.h" 11 | 12 | @class MYPackageTaskManager; 13 | @interface MYPackageBaseTask : NSObject 14 | 15 | @property (nonatomic, strong) MYPackageConfig *config; 16 | 17 | @property (nonatomic, weak) MYPackageTaskManager *taskManager; 18 | 19 | @property (nonatomic, strong) NSString *errorMessage; 20 | @property (nonatomic, strong) NSString *output; 21 | @property (nonatomic, readonly) BOOL isCancelled; 22 | @property (nonatomic, strong) NSString *name; 23 | 24 | - (void)cancel; 25 | - (BOOL)launch; 26 | 27 | - (void)logInfo:(NSString *)message; 28 | 29 | + (BOOL)shouldLaunchWithPreTaskStatus:(BOOL)status manager:(MYPackageTaskManager *)manager; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /Core/Task/MYPackageBaseTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageBaseTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/4. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBaseTask.h" 10 | 11 | @implementation MYPackageBaseTask 12 | 13 | - (void)cancel { 14 | _isCancelled = YES; 15 | [self.config.logger logN:@"User Cancel!"]; 16 | } 17 | 18 | - (BOOL)launch { 19 | _errorMessage = nil; 20 | return YES; 21 | } 22 | 23 | - (void)setErrorMessage:(NSString *)errorMessage { 24 | _errorMessage = errorMessage; 25 | [self.config.logger logN:@"❌ %@", errorMessage]; 26 | } 27 | 28 | - (void)logInfo:(NSString *)message { 29 | [self.config.logger logN:@"⚠️ %@", message]; 30 | } 31 | 32 | + (BOOL)shouldLaunchWithPreTaskStatus:(BOOL)status manager:(MYPackageTaskManager *)manager { 33 | return status; 34 | } 35 | 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Core/Task/MYPackageShellTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageShellTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/5. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBaseTask.h" 10 | 11 | #if __has_include() 12 | # import 13 | #else 14 | # import "ObjCShell.h" 15 | #endif 16 | 17 | 18 | extern NSDictionary *shellEnv; 19 | 20 | @interface MYPackageShellTask : MYPackageBaseTask 21 | 22 | @property (nonatomic, strong) ObjCShell *shellTask; 23 | @property (nonatomic, strong) NSString *workingDirectory; 24 | 25 | - (NSString *)scriptForName:(NSString *)name ofType:(NSString *)type; 26 | - (NSString *)command:(NSString *)command withAdministrator:(BOOL)administrator; 27 | - (int)executeCommand:(NSString *)command; 28 | - (int)executeCommand:(NSString *)command inWorkingDirectory:(NSString *)path; 29 | - (int)executeCommand:(NSString *)command inWorkingDirectory:(NSString *)path env:(NSDictionary *)env; 30 | 31 | - (int)executeRubyScript:(NSString *)script, ... NS_REQUIRES_NIL_TERMINATION; 32 | 33 | - (BOOL)updateGit:(NSString *)git local:(NSString *)local name:(NSString *)name isInit:(BOOL *)isInit; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /Core/Task/MYPackageTaskManager+TaskList.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageTaskManager+TaskList.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/2/6. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageTaskManager.h" 10 | 11 | extern NSArray *taskClassOrderSetup; 12 | extern NSArray *taskClassOrderPrefix; 13 | extern NSArray *taskClassOrderForApp; 14 | extern NSArray *taskClassOrderExportIPA; 15 | extern NSArray *taskClassOrderForLib; 16 | extern NSArray *taskClassOrderSuffix; 17 | 18 | @interface MYPackageTaskManager (TaskList) 19 | 20 | + (NSMutableArray *)taskClassNamesForTasks:(NSArray *)shortTasks; 21 | - (NSMutableArray *)taskClassOrder; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Core/Task/MYPackageTaskManager+TaskList.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageTaskManager+TaskList.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/2/6. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageTaskManager+TaskList.h" 10 | 11 | NSArray *taskClassOrderSetup; 12 | NSArray *taskClassOrderPrefix; 13 | NSArray *taskClassOrderForApp; 14 | NSArray *taskClassOrderExportIPA; 15 | NSArray *taskClassOrderForLib; 16 | NSArray *taskClassOrderSuffix; 17 | 18 | @implementation MYPackageTaskManager (TaskList) 19 | 20 | + (void)load { 21 | taskClassOrderSetup = @[ 22 | @"MYPackageInitTask", 23 | @"MYPackagePrepareEnvironmentTask", 24 | @"MYPackageCheckEnvironmentTask" 25 | ]; 26 | taskClassOrderPrefix = @[ 27 | // Prepare 28 | @"MYPackageInitTask", 29 | @"MYPackageCheckEnvironmentTask", 30 | @"MYPackageAnalyzeGitTask", 31 | @"MYPackageCheckGitTask", 32 | @"MYPackageListSchemeTask", 33 | @"MYPackageAnalyzeSchemeTask", 34 | @"MYPackageAnalyzeProjectTask", 35 | @"MYPackageAnalyzeTargetTask", 36 | ]; 37 | taskClassOrderForLib = @[ 38 | // Build 39 | @"MYPackageCleanTask", 40 | @"MYPackageBuildLibTask", 41 | @"MYPackageLipoTask", 42 | 43 | // Package 44 | @"MYPackageAnalyzeProductTask", 45 | @"MYPackageUpdatePlistTask", 46 | @"MYPackageZipTask", 47 | @"MYPackageCalculateSHA1Task", 48 | @"MYPackageCreateSpecTask", 49 | 50 | // release 51 | @"MYPackageCreateTagTask", 52 | ]; 53 | taskClassOrderForApp = @[ 54 | // Build 55 | @"MYPackageCleanTask", 56 | @"MYPackageBuildAppTask", 57 | @"MYPackageUpdatePlistTask", 58 | @"MYPackageResignAppTask", 59 | 60 | // release 61 | @"MYPackageUploadLocalTask", 62 | @"MYPackageCreateTagTask", 63 | ]; 64 | taskClassOrderSuffix = @[ 65 | // Clean 66 | @"MYPackageCleanIntermediateProductTask", 67 | ]; 68 | taskClassOrderExportIPA = @[ 69 | @"MYPackageInitTask", 70 | @"MYPackageCheckEnvironmentTask", 71 | @"MYPackageUpdatePlistTask", 72 | @"MYPackageResignAppTask", 73 | @"MYPackageUploadLocalTask", 74 | ]; 75 | } 76 | 77 | + (NSMutableArray *)taskClassNamesForTasks:(NSArray *)shortTasks { 78 | NSMutableArray *taskClassNames = [NSMutableArray arrayWithCapacity:[shortTasks count]]; 79 | for (NSString *taskName in shortTasks) { 80 | NSString *taskClassName = taskName; 81 | if (![taskClassName hasPrefix:@"MYPackage"]) { 82 | taskClassName = [taskClassName stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[taskClassName substringToIndex:1] uppercaseString]]; 83 | taskClassName = [NSString stringWithFormat:@"MYPackage%@Task", taskClassName]; 84 | } 85 | if (NSClassFromString(taskClassName)) { 86 | [taskClassNames addObject:taskClassName]; 87 | } 88 | } 89 | return taskClassNames; 90 | } 91 | 92 | - (NSMutableArray *)taskClassOrder { 93 | NSMutableArray *array = [NSMutableArray arrayWithArray:taskClassOrderPrefix]; 94 | if (self.config.appTarget) { 95 | [array addObjectsFromArray:taskClassOrderForApp]; 96 | } else { 97 | [array addObjectsFromArray:taskClassOrderForLib]; 98 | } 99 | [array addObjectsFromArray:taskClassOrderSuffix]; 100 | return array; 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /Core/Task/MYPackageTaskManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageTaskManager.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/10/22. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MYPackageBaseTask.h" 11 | #import "MYPackageConfig.h" 12 | 13 | #define MYPackageTaskManagerWillRunTaskNotification @"MYPackageTaskManagerWillRunTaskNotification" 14 | #define MYPackageTaskManagerFinishRunTaskNotification @"MYPackageTaskManagerFinishRunTaskNotification" 15 | 16 | @interface MYPackageTaskManager : NSObject 17 | 18 | @property (nonatomic, strong) MYPackageConfig *config; 19 | @property (nonatomic, strong) NSString *lastErrorMessage; 20 | @property (nonatomic, strong) NSString *output; 21 | 22 | - (BOOL)runTasks:(NSArray *)tasks; 23 | - (BOOL)runTaskClassNamesInOrder:(NSArray *)tasks; 24 | - (BOOL)runTaskClassNames:(NSArray *)tasks; 25 | - (void)cancelAllTask; 26 | 27 | + (void)registTasksObserver:(void(^)(NSMutableArray *tasks))observer id:(NSInteger)_id; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Core/Task/MYPackageTaskManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageTaskManager.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/10/22. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageTaskManager.h" 10 | #import "MYPackageTaskManager+TaskList.h" 11 | 12 | @interface MYPackageTaskManager () 13 | 14 | @property (nonatomic, readonly) MYPackageBaseTask *currentTask; 15 | 16 | @end 17 | 18 | @implementation MYPackageTaskManager 19 | 20 | + (NSMutableDictionary *)tasksObserver { 21 | static dispatch_once_t onceToken; 22 | static NSMutableDictionary *__tasksObserver; 23 | dispatch_once(&onceToken, ^{ 24 | __tasksObserver = [NSMutableDictionary dictionary]; 25 | }); 26 | return __tasksObserver; 27 | } 28 | 29 | + (void)registTasksObserver:(void(^)(NSMutableArray *tasks))observer id:(NSInteger)_id { 30 | [[self tasksObserver] setObject:observer forKey:@(_id)]; 31 | } 32 | 33 | - (BOOL)runTasks:(NSArray *)tasks { 34 | if (!tasks) { 35 | return [self runTaskClassNames:[self taskClassOrder]]; 36 | } 37 | NSMutableArray *taskClassNames = [NSMutableArray arrayWithCapacity:[tasks count]]; 38 | for (NSString *taskName in tasks) { 39 | NSString *taskClassName = [taskName stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[taskName substringToIndex:1] uppercaseString]]; 40 | taskClassName = [NSString stringWithFormat:@"MYPackage%@Task", taskClassName]; 41 | if (NSClassFromString(taskClassName)) { 42 | [taskClassNames addObject:taskClassName]; 43 | } else { 44 | [self.config.logger logN:@"任务 %@ 不存在,将被忽略!", taskName]; 45 | } 46 | } 47 | return [self runTaskClassNamesInOrder:taskClassNames]; 48 | } 49 | 50 | - (BOOL)runTaskClassNamesInOrder:(NSArray *)tasks { 51 | NSMutableArray *orderedTasks = [NSMutableArray arrayWithCapacity:tasks.count]; 52 | for (NSString *taskName in [self taskClassOrder]) { 53 | if ([tasks indexOfObject:taskName] != NSNotFound) { 54 | [orderedTasks addObject:taskName]; 55 | } 56 | } 57 | return [self runTaskClassNames:orderedTasks]; 58 | } 59 | 60 | - (BOOL)runTaskClassNames:(NSArray *)tasks { 61 | NSArray *sortedKeys = [[[[self class] tasksObserver] allKeys] sortedArrayUsingComparator:^NSComparisonResult(NSNumber *obj1, NSNumber *obj2) { 62 | return [obj1 integerValue] < [obj2 integerValue]; 63 | }]; 64 | NSArray *observers = [[[self class] tasksObserver] objectsForKeys:sortedKeys notFoundMarker:[NSNull null]]; 65 | NSMutableArray *ts = [tasks mutableCopy] ?: [NSMutableArray array]; 66 | for (void(^observer)(NSMutableArray *) in observers) { 67 | observer(ts); 68 | } 69 | BOOL result = YES; 70 | NSMutableString *output = [NSMutableString string]; 71 | for (NSString *taskName in ts) { 72 | Class taskClass = NSClassFromString(taskName); 73 | if (!taskClass) { 74 | continue; 75 | } 76 | if (![taskClass shouldLaunchWithPreTaskStatus:result manager:self]) { 77 | continue; 78 | } 79 | if (![self runTaskClass:taskClass]) { 80 | result = NO; 81 | } 82 | if ([self.currentTask.output length] > 0) { 83 | [output appendString:self.currentTask.output]; 84 | } 85 | if (self.currentTask.isCancelled) { 86 | break; 87 | } 88 | } 89 | self.output = output; 90 | return result; 91 | } 92 | 93 | - (BOOL)runTaskClass:(Class)taskClass { 94 | if (taskClass) { 95 | NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 96 | 97 | _currentTask = [[taskClass alloc] init]; 98 | [_currentTask setConfig:_config]; 99 | [_currentTask setTaskManager:self]; 100 | 101 | [center postNotificationName:MYPackageTaskManagerWillRunTaskNotification 102 | object:self 103 | userInfo:@{@"task": _currentTask}]; 104 | 105 | [self.config.logger logN:@"==================== Task: %@ ====================", NSStringFromClass(taskClass)]; 106 | NSDate *startDate = [NSDate date]; 107 | 108 | BOOL result = [_currentTask launch]; 109 | self.lastErrorMessage = _currentTask.errorMessage; 110 | 111 | [self.config.logger logN:@"---------------------- %f s ----------------------\n", [[NSDate date] timeIntervalSinceDate:startDate]]; 112 | 113 | [center postNotificationName:MYPackageTaskManagerFinishRunTaskNotification 114 | object:self 115 | userInfo:@{@"task": _currentTask, @"success": @(result)}]; 116 | 117 | return result; 118 | } 119 | return NO; 120 | } 121 | 122 | - (void)cancelAllTask { 123 | [self.currentTask cancel]; 124 | } 125 | 126 | @end 127 | -------------------------------------------------------------------------------- /Core/Task/Package/Feature/MYPackageIPAInfoTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageIPAInfoTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/2/8. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageIPAInfoTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Package/Feature/MYPackageIPAInfoTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageIPAInfoTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/2/8. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageIPAInfoTask.h" 10 | 11 | @implementation MYPackageIPAInfoTask 12 | 13 | - (BOOL)launch { 14 | if (![super launch]) { 15 | return NO; 16 | } 17 | 18 | NSData *data = nil; 19 | if ([[NSFileManager defaultManager] fileExistsAtPath:self.config.bundlePath]) { 20 | data = [self infoDataInIPA:self.config.bundlePath]; 21 | } else { 22 | data = [self infoDataInArchive:[[self.config.bundlePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"archive.xcarchive"]]; 23 | } 24 | if (!data) { 25 | return NO; 26 | } 27 | 28 | NSError *errorDesc = nil; 29 | NSDictionary *dict = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:nil error:&errorDesc]; 30 | if (errorDesc) { 31 | self.errorMessage = [errorDesc localizedDescription]; 32 | return NO; 33 | } 34 | self.config.displayName = dict[@"CFBundleDisplayName"]; 35 | self.config.appBundleId = dict[@"CFBundleIdentifier"]; 36 | self.config.version = dict[@"CFBundleShortVersionString"]; 37 | return YES; 38 | } 39 | 40 | - (NSData *)infoDataInArchive:(NSString *)path { 41 | MYPackageTarget *target = [self.config.selectedScheme.targets firstObject]; 42 | NSString *plistPath = [[[self.config.workspace.productDir stringByAppendingPathComponent:@"archive.xcarchive/Products"] stringByAppendingPathComponent:target.installPath] stringByAppendingPathComponent:target.infoPlistPath]; 43 | return [NSData dataWithContentsOfFile:plistPath]; 44 | } 45 | 46 | - (NSData *)infoDataInIPA:(NSString *)path { 47 | if ([self executeCommand:[NSString stringWithFormat:@"zipinfo -1 \"%@\" \"Payload/*.app/\" \"Payload/*.appex/\"", path]] != 0) { 48 | self.errorMessage = self.shellTask.errorString; 49 | return nil; 50 | } 51 | if ([self.shellTask.outputString length] == 0) { 52 | self.errorMessage = @"分析 IPA 出错,未找到 app"; 53 | return nil; 54 | } 55 | NSString *infoPath = [[self.shellTask.outputString componentsSeparatedByString:@"\n"] firstObject]; 56 | if ([self executeCommand:[NSString stringWithFormat:@"unzip -p \"%@\" \"%@Info.plist\"", self.config.bundlePath, infoPath]] != 0) { 57 | self.errorMessage = self.shellTask.errorString; 58 | return nil; 59 | } 60 | return self.shellTask.outputData; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /Core/Task/Package/Feature/MYPackageListProfilesTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageListProfilesTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/2/8. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | #import "MYPackageProvisioningProfile.h" 11 | 12 | @interface MYPackageListProfilesTask : MYPackageShellTask 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Core/Task/Package/Feature/MYPackageListProfilesTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageListProfilesTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/2/8. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageListProfilesTask.h" 10 | 11 | @implementation MYPackageListProfilesTask 12 | 13 | - (BOOL)launch { 14 | if (![super launch]) { 15 | return NO; 16 | } 17 | if ([self executeRubyScript:@"list_provisioning_profiles", nil] != 0) { 18 | self.errorMessage = @"获取描述文件列表失败!"; 19 | return NO; 20 | } 21 | return YES; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Core/Task/Package/MYPackageAnalyzeProductTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageAnalyzeProductTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/6. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageAnalyzeProductTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Package/MYPackageAnalyzeProductTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageAnalyzeProductTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/6. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageAnalyzeProductTask.h" 10 | 11 | @implementation MYPackageAnalyzeProductTask 12 | 13 | - (NSString *)name { 14 | return @"分析系统依赖"; 15 | } 16 | 17 | - (BOOL)analyzeProjectSettingInfo:(MYPackageProject *)project { 18 | if ([self executeRubyScript:@"xcode-analyze", project.filePath, nil] != 0) { 19 | return NO; 20 | } 21 | NSError *error; 22 | NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:self.shellTask.outputData 23 | options:NSJSONReadingAllowFragments 24 | error:&error]; 25 | if (error) { 26 | self.errorMessage = [NSString stringWithFormat:@"JSON 格式非法: %@", [error localizedDescription]]; 27 | return NO; 28 | } 29 | project.info = dic; 30 | return YES; 31 | } 32 | 33 | - (BOOL)launch { 34 | if (![super launch]) { 35 | return NO; 36 | } 37 | NSMutableArray *projects = [NSMutableArray array]; 38 | [self.config.selectedScheme.targets enumerateObjectsUsingBlock:^(MYPackageTarget * _Nonnull target, NSUInteger idx, BOOL * _Nonnull stop) { 39 | if ([projects indexOfObject:target.project] == NSNotFound) { 40 | [projects addObject:target.project]; 41 | } 42 | }]; 43 | for (MYPackageProject *project in projects) { 44 | if (![self analyzeProjectSettingInfo:project]) { 45 | return NO; 46 | } 47 | } 48 | 49 | return YES; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /Core/Task/Package/MYPackageCalculateSHA1Task.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCalculateSHA1Task.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 2016/12/12. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageCalculateSHA1Task : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Package/MYPackageCalculateSHA1Task.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCalculateSHA1Task.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 2016/12/12. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageCalculateSHA1Task.h" 10 | 11 | @implementation MYPackageCalculateSHA1Task 12 | 13 | - (NSString *)name { 14 | return @"计算 SHA1"; 15 | } 16 | 17 | - (BOOL)launch { 18 | if (![super launch]) { 19 | return NO; 20 | } 21 | if ([self executeCommand:[NSString stringWithFormat:@"openssl sha1 \"%@\"", self.config.bundlePath]] != 0) { 22 | self.errorMessage = self.shellTask.errorString; 23 | return NO; 24 | } 25 | self.config.bundleHash = [[self.output componentsSeparatedByString:@" "] lastObject]; 26 | return YES; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Core/Task/Package/MYPackageCreateDistributePlistTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCreateDistributePlistTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/5/19. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBaseTask.h" 10 | 11 | @interface MYPackageCreateDistributePlistTask : MYPackageBaseTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Package/MYPackageCreateDistributePlistTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCreateDistributePlistTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/5/19. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageCreateDistributePlistTask.h" 10 | 11 | @implementation MYPackageCreateDistributePlistTask 12 | 13 | - (NSString *)name { 14 | return @"创建 Plist"; 15 | } 16 | 17 | - (BOOL)launch { 18 | if (![super launch]) { 19 | return NO; 20 | } 21 | 22 | return YES; 23 | } 24 | @end 25 | -------------------------------------------------------------------------------- /Core/Task/Package/MYPackageCreateSpecTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCreateSpecTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/6. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageCreateSpecTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Package/MYPackageResignAppTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageResignAppTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/2/7. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageResignAppTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Package/MYPackageResignAppTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageResignAppTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/2/7. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageResignAppTask.h" 10 | 11 | @implementation MYPackageResignAppTask 12 | 13 | - (BOOL)launch { 14 | if (![super launch]) { 15 | return NO; 16 | } 17 | 18 | if ([self.config.signType length] == 0 || [self.config.teamID length] == 0) { 19 | [self logInfo:@"未设置 signType 或者 teamId,不导出 IPA!"]; 20 | return YES; 21 | } 22 | 23 | [[NSFileManager defaultManager] createDirectoryAtPath:self.config.productsDir withIntermediateDirectories:YES attributes:nil error:nil]; 24 | 25 | NSDictionary *options = @{ 26 | @"method": self.config.signType, 27 | @"teamID": self.config.teamID, 28 | @"compileBitcode": @(NO) 29 | }; 30 | NSString *optionsPath = [self.config.productsDir stringByAppendingPathComponent:@"exportOptions.plist"]; 31 | [options writeToFile:optionsPath atomically:YES]; 32 | 33 | if ([self executeCommand:[NSString stringWithFormat:@"set -o pipefail && xcodebuild -archivePath \"%@\" -exportPath \"%@\" -exportOptionsPlist \"%@\" -exportArchive | bundle exec xcpretty", 34 | self.config.archivePath, 35 | self.config.productsDir, 36 | optionsPath 37 | ]] != 0) { 38 | self.errorMessage = @"编译项目出错,详见日志!"; 39 | return NO; 40 | } 41 | return YES; 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /Core/Task/Package/MYPackageUpdatePlistTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageUpdatePlistTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/10/27. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageUpdatePlistTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Package/MYPackageZipTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageZipTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/6. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageZipTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Package/MYPackageZipTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageZipTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/6. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageZipTask.h" 10 | 11 | @implementation MYPackageZipTask 12 | 13 | - (NSString *)name { 14 | return @"打包压缩"; 15 | } 16 | 17 | - (BOOL)launch { 18 | if (![super launch]) { 19 | return NO; 20 | } 21 | 22 | NSFileManager *fileManager = [NSFileManager defaultManager]; 23 | NSError *error = nil; 24 | 25 | [fileManager createDirectoryAtPath:self.config.productsDir withIntermediateDirectories:YES attributes:nil error:nil]; 26 | 27 | [fileManager removeItemAtPath:self.config.bundlePath error:&error]; 28 | 29 | for (MYPackageTarget *target in self.config.selectedScheme.targets) { 30 | NSString *productDir = [self.config productPathForTarget:target]; 31 | if (![[NSFileManager defaultManager] fileExistsAtPath:[productDir stringByAppendingPathComponent:target.fullProductName]]) { 32 | self.errorMessage = [NSString stringWithFormat:@"输出结果 %@ 不存在!", target.fullProductName]; 33 | return NO; 34 | } 35 | 36 | // 复制本地链接的 Framework 37 | for (NSString *path in [target.localFrameworks allValues]) { 38 | if (![fileManager fileExistsAtPath:path]) { 39 | self.errorMessage = [NSString stringWithFormat:@"链接的本地 Framework 不存在:%@", path]; 40 | return NO; 41 | } 42 | NSString *toPath = [productDir stringByAppendingPathComponent:[path lastPathComponent]]; 43 | [self.config.logger logN:@"cp '%@' '%@'", path, toPath]; 44 | [fileManager removeItemAtPath:toPath error:nil]; 45 | if (![fileManager copyItemAtPath:path 46 | toPath:toPath 47 | error:&error]) { 48 | self.errorMessage = [error localizedDescription]; 49 | return NO; 50 | } 51 | } 52 | } 53 | NSString *zipCmd = [NSString stringWithFormat:@"zip -r -X '%@' * -x \"*.DS_Store\"", self.config.bundlePath]; 54 | 55 | if ([self executeCommand:zipCmd inWorkingDirectory:self.config.productsDir] != 0) { 56 | self.errorMessage = [NSString stringWithFormat:@"打包 %@ 失败!", [self.config.bundlePath substringFromIndex:[self.config.outputDir length]]]; 57 | return NO; 58 | } 59 | return YES; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /Core/Task/Package/Shell/list_provisioning_profiles.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | #!/usr/bin/ruby 3 | 4 | require './parse_provisioning_profile.rb' 5 | 6 | PROVISIONS = "#{Dir.home}/Library/MobileDevice/Provisioning\ Profiles/".freeze 7 | 8 | def get_profiles 9 | certs = [] 10 | `/usr/bin/security find-identity -v -p codesigning`.split("\n").each do |line| 11 | match = line.match(/\s+\d+\) (\h+) "(.*)"$/) 12 | next if match.nil? 13 | certs << match[1] 14 | end 15 | 16 | profiles = {} 17 | Dir["#{PROVISIONS}*"].each do |p| 18 | profile = ProvisionParser.parse(p) 19 | unless profile.nil? 20 | profile.certs = certs 21 | (profiles[profile.team_id] ||= []) << profile 22 | end 23 | end 24 | profiles.each do |team_id, array| 25 | array.sort!{ |p1, p2| "#{p1.bundle_id}#{p1.expiration_date}" <=> "#{p2.bundle_id}#{p2.expiration_date}" }.reverse! 26 | end 27 | profiles 28 | end 29 | 30 | if __FILE__ == $0 31 | require 'json' 32 | puts get_profiles.to_json 33 | end 34 | -------------------------------------------------------------------------------- /Core/Task/Package/Shell/parse_provisioning_profile.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | require 'open3' 4 | require 'plist' 5 | require 'base64' 6 | require 'digest/sha1' 7 | 8 | class ProvisionParser 9 | def initialize(path, dict) 10 | @path = path 11 | @dict = dict 12 | end 13 | 14 | def path 15 | @path 16 | end 17 | 18 | def name 19 | @dict["Name"] 20 | end 21 | 22 | def expired? 23 | DateTime.now > expiration_date 24 | end 25 | 26 | def expiration_date 27 | @dict["ExpirationDate"] 28 | end 29 | 30 | def entitlements 31 | @dict["Entitlements"] 32 | end 33 | 34 | def team_id 35 | entitlements["com.apple.developer.team-identifier"] 36 | end 37 | 38 | def bundle_id 39 | bundle_id = entitlements["application-identifier"] 40 | bundle_id[bundle_id.index(".")+1 .. -1] 41 | end 42 | 43 | def certs 44 | @dict["DeveloperCertificates"] 45 | end 46 | 47 | def certs=(certs) 48 | @dict["DeveloperCertificates"] &= certs 49 | end 50 | 51 | def to_json(args={}) 52 | @dict.to_json 53 | end 54 | 55 | def self.parse(path) 56 | o, e, s = Open3.capture3("openssl smime -inform der -verify -noverify -in \"#{path}\" 2>/dev/null") 57 | if s.exitstatus != 0 58 | raise "Can't extract plist from provision profile\n" + e 59 | end 60 | 61 | # 将证书字段作为字符串处理 62 | o.gsub!("", "").gsub!("", "") 63 | 64 | provision_plist = Plist.parse_xml(o) 65 | 66 | # 去除设备列表,减小字符串长度 67 | provision_plist["ProvisionedDevices"] = provision_plist["ProvisionedDevices"].count if provision_plist["ProvisionedDevices"] 68 | 69 | # 替换证书为 SHA1 列表 70 | provision_plist['DeveloperCertificates'] = provision_plist['DeveloperCertificates'].map do |object| 71 | Digest::SHA1.hexdigest(Base64.decode64(object)).upcase 72 | end 73 | 74 | self.new(path, provision_plist) 75 | end 76 | end 77 | 78 | if __FILE__ == $0 79 | require 'json' 80 | puts ProvisionParser.parse(ARGS[1]).to_json 81 | end 82 | -------------------------------------------------------------------------------- /Core/Task/Package/Shell/xcode-analyze.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'xcodeproj' 4 | require 'json' 5 | 6 | proj = Xcodeproj::Project.open(ARGV[0]) 7 | json = proj.pretty_print 8 | 9 | frameworks = {} 10 | proj.files.select {|ref|ref.path =~ /\.(framework|a|tbd)$/i}.map {|ref| frameworks[ref.name || File.basename(ref.path)] = ref.path } 11 | json["Frameworks"] = frameworks 12 | 13 | puts json.to_json 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Feature/MYPackageCleanInvalidCertTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCleanInvalidCertTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/5/17. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageCleanInvalidCertTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Feature/MYPackageCleanInvalidCertTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCleanInvalidCertTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/5/17. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageCleanInvalidCertTask.h" 10 | 11 | @implementation MYPackageCleanInvalidCertTask 12 | 13 | - (BOOL)launch { 14 | if (![super launch]) { 15 | return NO; 16 | } 17 | if ([self executeRubyScript:@"clean_invalid_cert", nil] != 0) { 18 | self.errorMessage = self.shellTask.errorString; 19 | return NO; 20 | } 21 | return YES; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Feature/MYPackageCleanInvalidProfileTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCleanInvalidProfileTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/5/17. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageCleanInvalidProfileTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Feature/MYPackageCleanInvalidProfileTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCleanInvalidProfileTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/5/17. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageCleanInvalidProfileTask.h" 10 | 11 | @implementation MYPackageCleanInvalidProfileTask 12 | 13 | - (BOOL)launch { 14 | if (![super launch]) { 15 | return NO; 16 | } 17 | if ([self executeRubyScript:@"clean_invalid_profile", nil] != 0) { 18 | self.errorMessage = self.shellTask.errorString; 19 | return NO; 20 | } 21 | return YES; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Feature/MYPackageGetHighestVersionTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageGetHighestVersionTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/2. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageGetHighestVersionTask : MYPackageShellTask 12 | 13 | @property (nonatomic, strong) NSString *podName; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Feature/MYPackageGetHighestVersionTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageGetHighestVersionTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/2. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageGetHighestVersionTask.h" 10 | 11 | @implementation MYPackageGetHighestVersionTask 12 | 13 | - (BOOL)launch { 14 | if (![super launch]) { 15 | return NO; 16 | } 17 | if ([self executeRubyScript:@"find_highest_version_in_repo", self.podName, nil] != 0) { 18 | [self.config.logger logN:@"Find highest version fail! %@", self.errorMessage]; 19 | return NO; 20 | } 21 | return YES; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Feature/MYPackageIPAInfoTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageIPAInfoTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/5/12. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageIPAInfoTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Feature/MYPackageIPAInfoTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageIPAInfoTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/5/12. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageIPAInfoTask.h" 10 | 11 | @implementation MYPackageIPAInfoTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Feature/MYPackageListOpenedProjectTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageListOpenedProjectTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/10/22. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBaseTask.h" 10 | 11 | @interface MYPackageListOpenedProjectTask : MYPackageBaseTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Feature/MYPackageListOpenedProjectTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageListOpenedProjectTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/10/22. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageListOpenedProjectTask.h" 10 | 11 | #if __has_include() 12 | # import 13 | #else 14 | # import "ObjCAppleScript.h" 15 | #endif 16 | 17 | @implementation MYPackageListOpenedProjectTask 18 | 19 | - (BOOL)launch { 20 | if (![super launch]) { 21 | return NO; 22 | } 23 | NSDictionary *errorDict = nil; 24 | NSArray *list = [ObjCAppleScript executeWithSource:@"on is_running(appName)\n\ 25 | tell application \"System Events\" to (name of processes) contains appName\n\ 26 | end is_running\n\ 27 | set xcodeRunning to is_running(\"Xcode\")\n\ 28 | if xcodeRunning then\n\ 29 | tell application id \"com.apple.dt.Xcode\"\n\ 30 | return the path of every workspace document\n\ 31 | end tell\n\ 32 | else\n\ 33 | return {}\n\ 34 | end if" 35 | error:&errorDict]; 36 | [self.config.logger logN:[list description]]; 37 | if (errorDict) { 38 | self.errorMessage = errorDict[NSAppleScriptErrorBriefMessage]; 39 | return NO; 40 | } 41 | NSData *data = [NSJSONSerialization dataWithJSONObject:list options:0 error:nil]; 42 | self.output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 43 | 44 | return YES; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Feature/MYPackageListProfilesTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageListProfilesTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/5/12. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | #import "MYPackageProfile.h" 11 | 12 | @interface MYPackageListProfilesTask : MYPackageShellTask 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Feature/MYPackageListProfilesTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageListProfilesTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/5/12. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageListProfilesTask.h" 10 | 11 | @implementation MYPackageListProfilesTask 12 | 13 | - (BOOL)launch { 14 | if (![super launch]) { 15 | return NO; 16 | } 17 | if ([self executeRubyScript:@"list_provisioning_profiles", nil] != 0) { 18 | self.errorMessage = self.output; 19 | return NO; 20 | } 21 | return YES; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackageAnalyzeGitTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageAnalyzeGitTask.h 3 | // 4 | // 5 | // Created by Whirlwind on 15/9/9. 6 | // 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageAnalyzeGitTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackageAnalyzeGitTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageAnalyzeGitTask.m 3 | // 4 | // 5 | // Created by Whirlwind on 15/9/9. 6 | // 7 | // 8 | 9 | #import "MYPackageAnalyzeGitTask.h" 10 | 11 | @implementation MYPackageAnalyzeGitTask 12 | 13 | - (NSString *)name { 14 | return @"分析 Git 信息"; 15 | } 16 | 17 | - (BOOL)launch { 18 | if (![super launch]) { 19 | return NO; 20 | } 21 | 22 | if (![[NSFileManager defaultManager] fileExistsAtPath:self.config.workspace.filePath]) { 23 | self.errorMessage = [NSString stringWithFormat:@"项目文件不存在:%@", self.config.workspace.filePath]; 24 | return NO; 25 | } 26 | self.workingDirectory = self.config.selectedScheme.container ?: self.config.workspace.path; 27 | 28 | // Git Commit Hash 29 | if ([self executeCommand:@"git rev-parse HEAD"] != 0) { 30 | self.errorMessage = @"获取 Git Commit Hash 失败!"; 31 | } else { 32 | self.config.commitHash = self.shellTask.outputString; 33 | } 34 | 35 | // 用户名 36 | if (!self.config.authorName) { 37 | if ([self executeCommand:@"git config user.name"] != 0) { 38 | self.errorMessage = @"获取 Git 用户名失败,请先设置用户信息!"; 39 | return NO; 40 | } 41 | self.config.authorName = self.shellTask.outputString; 42 | } 43 | 44 | // 用户邮箱 45 | if (!self.config.authorEmail) { 46 | if ([self executeCommand:@"git config user.email"] != 0) { 47 | self.errorMessage = @"获取 Git 邮箱失败,请先设置用户信息!"; 48 | return NO; 49 | } 50 | self.config.authorEmail = self.shellTask.outputString; 51 | } 52 | 53 | // 主页 54 | if ([self executeCommand:@"git config remote.origin.url"] != 0) { 55 | self.errorMessage = @"获取 Git 服务器地址失败,请先设置服务端地址!"; 56 | return NO; 57 | } else { 58 | self.config.gitUrl = [self.shellTask.outputString lowercaseString]; 59 | NSString *homePage = self.config.gitUrl; 60 | if ([homePage hasPrefix:@"git@"]) { 61 | homePage = [homePage substringFromIndex:4]; 62 | } 63 | if ([homePage hasSuffix:@".git"]) { 64 | homePage = [homePage substringToIndex:[homePage length] - 4]; 65 | } 66 | homePage = [homePage stringByReplacingOccurrencesOfString:@":(?=[^/])" withString:@"/" options:NSRegularExpressionSearch range:NSMakeRange(0, [homePage length])]; 67 | if ([homePage rangeOfString:@"://"].location == NSNotFound) { 68 | homePage = [NSString stringWithFormat:@"http://%@", homePage]; 69 | } 70 | NSURL *url = [NSURL URLWithString:homePage]; 71 | NSString *scheme = url.scheme; 72 | if ([scheme isEqualToString:@"ssh"]) { 73 | scheme = @"http"; 74 | } 75 | if ([self executeCommand:[NSString stringWithFormat:@"ssh -T -G \"%@\" | awk '/^hostname / { print $2 }'", url.host]] != 0) { 76 | self.errorMessage = @"解析 ssh Host 失败!"; 77 | return NO; 78 | } 79 | NSURLComponents *urlComponents = [[NSURLComponents alloc] init]; 80 | urlComponents.scheme = scheme; 81 | urlComponents.host = self.shellTask.outputString; 82 | urlComponents.path = url.path; 83 | self.config.homePage = [urlComponents string]; 84 | } 85 | return YES; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackageAnalyzeProjectTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageAnalyzeProjectTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/7. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageAnalyzeProjectTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackageAnalyzeProjectTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageAnalyzeProjectTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/5/7. 6 | // Copyright (c) 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageAnalyzeProjectTask.h" 10 | 11 | @implementation MYPackageAnalyzeProjectTask 12 | 13 | - (NSString *)name { 14 | return [NSString stringWithFormat:@"分析项目 %@ ", [self.config.workspace.filePath lastPathComponent]]; 15 | } 16 | 17 | - (BOOL)launch { 18 | if (![super launch]) { 19 | return NO; 20 | } 21 | NSArray *projectPaths = [[self.config.selectedScheme.targetNames allValues] valueForKeyPath:@"@distinctUnionOfObjects.self"]; 22 | for (NSString *projectPath in projectPaths) { 23 | if (![self analyzeProjectPath:projectPath]) { 24 | return NO; 25 | } 26 | } 27 | return YES; 28 | } 29 | 30 | - (BOOL)analyzeProjectPath:(NSString *)projectPath { 31 | if ([self executeCommand:[NSString stringWithFormat:@"xcodebuild -project '%@' -list", projectPath]] != 0) { 32 | self.errorMessage = @"XCode 分析项目出错,详见日志"; 33 | return NO; 34 | } 35 | 36 | MYPackageProject *project = [[MYPackageProject alloc] init]; 37 | [project setFilePath:projectPath]; 38 | [self.config.workspace.projects setObject:project forKey:projectPath]; 39 | 40 | NSRegularExpression *regular; 41 | 42 | // 分析项目名称 43 | regular = [NSRegularExpression regularExpressionWithPattern:@"Information about project \"(.+)\":\n" 44 | options:NSRegularExpressionCaseInsensitive 45 | error:nil]; 46 | NSRange range = [regular rangeOfFirstMatchInString:self.shellTask.outputString 47 | options:0 48 | range:NSMakeRange(0, self.shellTask.outputString.length)]; 49 | if (range.location == NSNotFound || range.length == 0) { 50 | self.errorMessage = @"分析项目名称出错!"; 51 | return NO; 52 | } 53 | NSString *name = [regular stringByReplacingMatchesInString:[self.shellTask.outputString substringWithRange:range] 54 | options:0 55 | range:NSMakeRange(0, range.length) 56 | withTemplate:@"$1"]; 57 | [project setName:name]; 58 | 59 | // 分析 Target 列表 60 | NSArray *targets = [self parseString:self.shellTask.outputString forKeyword:@"Targets"]; 61 | if (!targets) { 62 | return NO; 63 | } 64 | [project setTargetNames:targets]; 65 | 66 | // 分析 Configuration 列表 67 | NSArray *configrations = [self parseString:self.shellTask.outputString forKeyword:@"Build Configurations"]; 68 | if (!configrations) { 69 | return NO; 70 | } 71 | [project setConfigrations:configrations]; 72 | 73 | return YES; 74 | } 75 | 76 | - (NSArray *)parseString:(NSString *)string forKeyword:(NSString *)keyword { 77 | NSRegularExpression *regular = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"^ *%@:\n", keyword] 78 | options:NSRegularExpressionAnchorsMatchLines 79 | error:nil]; 80 | NSRange range = [regular rangeOfFirstMatchInString:string options:0 range:NSMakeRange(0, string.length)]; 81 | if (range.location == NSNotFound) { 82 | self.errorMessage = [NSString stringWithFormat:@"分析项目 %@ 出错!", keyword]; 83 | return nil; 84 | } 85 | NSMutableArray *array = [NSMutableArray array]; 86 | for (NSString *line in [[self.shellTask.outputString substringFromIndex:NSMaxRange(range)] componentsSeparatedByString:@"\n"]) { 87 | NSString *string = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 88 | if ([string length] == 0) { 89 | break; 90 | } 91 | [array addObject:string]; 92 | } 93 | if ([array count] == 0) { 94 | self.errorMessage = [NSString stringWithFormat:@"项目 %@ 是空的!", keyword]; 95 | return nil; 96 | } 97 | return array; 98 | } 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackageAnalyzeSchemeTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageAnalyzeSchemeTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/23. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageAnalyzeSchemeTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackageAnalyzeSchemeTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageAnalyzeSchemeTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/23. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageAnalyzeSchemeTask.h" 10 | 11 | @implementation MYPackageAnalyzeSchemeTask 12 | 13 | - (NSString *)name { 14 | return [NSString stringWithFormat:@"分析 %@", self.config.selectedScheme.name]; 15 | } 16 | 17 | - (BOOL)launch { 18 | if (![super launch]) { 19 | return NO; 20 | } 21 | if (!self.config.selectedScheme) { 22 | if (self.config.selectedSchemeName) { 23 | self.errorMessage = [NSString stringWithFormat:@"不存在 Scheme:%@", self.config.selectedSchemeName]; 24 | } else { 25 | self.errorMessage = @"需要指明 Scheme!"; 26 | } 27 | return NO; 28 | } 29 | 30 | if ([self executeRubyScript:@"analyze_scheme", self.config.selectedScheme.filePath, nil] != 0) { 31 | self.errorMessage = [NSString stringWithFormat:@"分析 Scheme 失败!%@", self.output]; 32 | return NO; 33 | } 34 | 35 | NSError *error; 36 | 37 | NSDictionary *targets = [NSJSONSerialization JSONObjectWithData:self.shellTask.outputData 38 | options:0 39 | error:&error]; 40 | if (error) { 41 | self.errorMessage = [NSString stringWithFormat:@"JSON 格式非法: %@", [error localizedDescription]]; 42 | return NO; 43 | } 44 | NSMutableDictionary *ts = [NSMutableDictionary dictionaryWithCapacity:targets.count]; 45 | [targets enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, NSString *_Nonnull obj, BOOL *_Nonnull stop) { 46 | NSString *path = [[[self.config.selectedScheme.container stringByDeletingLastPathComponent] stringByAppendingPathComponent:obj] stringByStandardizingPath]; 47 | [ts setObject:path forKey:key]; 48 | }]; 49 | self.config.selectedScheme.targetNames = ts; 50 | return YES; 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackageAnalyzeTargetTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageAnalyzeTargetTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/3/15. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageAnalyzeTargetTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackageCheckEnvironmentTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCheckEnvironmentTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/4/18. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageCheckEnvironmentTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackageCheckEnvironmentTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCheckEnvironmentTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/4/18. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageCheckEnvironmentTask.h" 10 | 11 | @implementation MYPackageCheckEnvironmentTask 12 | 13 | - (BOOL)launch { 14 | if (![super launch]) { 15 | return NO; 16 | } 17 | if (![[NSFileManager defaultManager] fileExistsAtPath:shellEnv[@"BUNDLE_GEMFILE"]]) { 18 | self.errorMessage = [NSString stringWithFormat:@"Ruby Gems 目录不存在,请联系管理员!%@", shellEnv[@"BUNDLE_GEMFILE"]]; 19 | return NO; 20 | } 21 | if ([self executeCommand:@"bundle check"] != 0) { 22 | self.errorMessage = @"Gem 环境不正常,请联系管理员!"; 23 | return NO; 24 | } 25 | return YES; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackageCheckGitTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCheckGitTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/12/8. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageCheckGitTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackageCheckGitTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCheckGitTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/12/8. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageCheckGitTask.h" 10 | 11 | @implementation MYPackageCheckGitTask 12 | 13 | - (NSString *)name { 14 | return @"检测 Git 状态"; 15 | } 16 | 17 | - (BOOL)launch { 18 | if (![super launch]) { 19 | return NO; 20 | } 21 | 22 | self.workingDirectory = self.config.selectedScheme.container ?: self.config.workspace.path; 23 | 24 | if ([self executeCommand:@"git fetch"] != 0) { 25 | self.errorMessage = @"更新 Git 失败!"; 26 | return NO; 27 | } 28 | 29 | // 检查 git 是否都已提交 30 | if ([self executeCommand:@"{ git diff --name-only ; git diff --name-only --staged ; } | sort"] != 0) { 31 | self.errorMessage = @"检查 Git 状态失败!"; 32 | return NO; 33 | } 34 | if ([self.shellTask.outputString length] > 0) { 35 | self.errorMessage = @"有代码未 Commit,请先 Commit 并 Push!"; 36 | return NO; 37 | } 38 | 39 | // 检查 git 是否和远程同步 40 | if ([self executeCommand:@"git branch -r --contains $(git rev-parse --short HEAD)"] != 0) { 41 | self.errorMessage = @"检查 Git 同步状态失败!"; 42 | return NO; 43 | } 44 | if ([self.shellTask.outputString length] == 0) { 45 | self.errorMessage = @"Git 本地和远程不同步,请先保持同步!"; 46 | return NO; 47 | } 48 | 49 | return YES; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackageInitTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageInitTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/3/30. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageInitTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackageInitTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageInitTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/3/30. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageInitTask.h" 10 | #import "GetPrimaryMACAddress.h" 11 | #import "GetIPAddress.h" 12 | 13 | @implementation MYPackageInitTask 14 | 15 | - (BOOL)launch { 16 | if (![super launch]) { 17 | return NO; 18 | } 19 | [self logInfo]; 20 | 21 | if ([self executeCommand:@"printenv" inWorkingDirectory:nil env:nil] != 0) { 22 | self.errorMessage = [NSString stringWithFormat:@"获取环境变量失败!%@", self.shellTask.errorString]; 23 | return NO; 24 | } 25 | NSMutableDictionary *env = [NSMutableDictionary dictionary]; 26 | [[self.output componentsSeparatedByString:@"\n"] enumerateObjectsUsingBlock:^(NSString *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { 27 | NSInteger index = [obj rangeOfString:@"="].location; 28 | if (index != NSNotFound) { 29 | [env setObject:[obj substringFromIndex:index + 1] forKey:[obj substringToIndex:index]]; 30 | } 31 | }]; 32 | if ([env count] == 0) { 33 | self.errorMessage = @"获取的环境变量为空"; 34 | return NO; 35 | } 36 | [env addEntriesFromDictionary:@{@"LANG": @"en_US.UTF-8", 37 | @"LANGUAGE": @"en_US.UTF-8", 38 | @"LC_ALL": @"en_US.UTF-8", 39 | @"BUNDLE_GEMFILE": [[[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] stringByAppendingPathComponent:@"GemEnv/Gemfile"], 40 | @"MallocNanoZone": @"0", 41 | @"DISABLE_AUTO_UPDATE": @"true"}]; 42 | shellEnv = env; 43 | [ObjCShell setSHELL:env[@"SHELL"]]; 44 | 45 | return YES; 46 | } 47 | 48 | - (void)logInfo { 49 | 50 | [self.config.logger logN:@"Name:\t%@", [[NSHost currentHost] localizedName]]; 51 | 52 | NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; 53 | 54 | [self.config.logger logN:@"OS:\t%@", [NSString stringWithFormat:@"%zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion]]; 55 | [self.config.logger logN:@"MAC:\t%@", macAddress()]; 56 | [self.config.logger logN:@"IP:\t%@", IPAddress()]; 57 | 58 | NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; 59 | NSString *appBuild = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; 60 | [self.config.logger logN:@"App:\t%@ (%@)", appVersion, appBuild]; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackageListSchemeTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageListSchemeTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/12/17. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageListSchemeTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackageListSchemeTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageListSchemeTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/12/17. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageListSchemeTask.h" 10 | 11 | @implementation MYPackageListSchemeTask 12 | 13 | // 分析 Scheme 列表 14 | // 只列出 Shared 的,所以不取 xcodebuild 输出的列表 15 | - (BOOL)launch { 16 | if (![super launch]) { 17 | return NO; 18 | } 19 | NSMutableArray *schemes = [NSMutableArray array]; 20 | [schemes addObjectsFromArray:[self sharedSchemesAtPath:self.config.workspace.filePath]]; 21 | if (PathIsWorkspace(self.config.workspace.filePath)) { 22 | for (NSString *path in self.config.workspace.projectPaths) { 23 | [schemes addObjectsFromArray:[self sharedSchemesAtPath:path]]; 24 | } 25 | } 26 | if (self.errorMessage) { 27 | return NO; 28 | } 29 | if ([schemes count] == 0) { 30 | self.errorMessage = @"无 Shared 的 Scheme!"; 31 | return NO; 32 | } 33 | [self.config.workspace setSchemes:schemes]; 34 | return YES; 35 | } 36 | 37 | - (NSArray *)sharedSchemesAtPath:(NSString *)path { 38 | NSString *schemeDir = [path stringByAppendingPathComponent:@"xcshareddata/xcschemes"]; 39 | [self.config.logger log:@"扫描 %@ ...", schemeDir]; 40 | if (![[NSFileManager defaultManager] fileExistsAtPath:schemeDir]) { 41 | [self.config.logger logN:@"不存在!"]; 42 | return nil; 43 | } 44 | NSError *error; 45 | NSArray *schemes = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:schemeDir error:&error]; 46 | if (error) { 47 | self.errorMessage = [error localizedDescription]; 48 | return nil; 49 | } 50 | schemes = [schemes filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF ENDSWITH '.xcscheme'"]]; 51 | if ([schemes count] == 0) { 52 | [self.config.logger logN:@"不包含 scheme !"]; 53 | return nil; 54 | } 55 | [self.config.logger logN:@""];// 换行 56 | [self.config.logger logN:@"%@", schemes]; 57 | NSMutableArray *s = [NSMutableArray arrayWithCapacity:[schemes count]]; 58 | [schemes enumerateObjectsUsingBlock:^(NSString *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { 59 | MYPackageScheme *scheme = [[MYPackageScheme alloc] init]; 60 | scheme.filePath = [schemeDir stringByAppendingPathComponent:obj]; 61 | scheme.container = path; 62 | [s addObject:scheme]; 63 | }]; 64 | return s; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /Core/Task/Prepare/MYPackagePrepareEnvironmentTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackagePrepareEnvironmentTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/23. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackagePrepareEnvironmentTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_PATH: "vendor/bundle" 3 | BUNDLE_DISABLE_SHARED_GEMS: "true" 4 | BUNDLE_JOBS: "4" 5 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://gems.ruby-china.org" 2 | 3 | gem 'cocoapods' 4 | gem 'xcpretty' 5 | gem 'plist' 6 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://gems.ruby-china.org/ 3 | specs: 4 | CFPropertyList (3.0.0) 5 | activesupport (4.2.10) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | atomos (0.1.2) 11 | claide (1.0.2) 12 | cocoapods (1.5.3) 13 | activesupport (>= 4.0.2, < 5) 14 | claide (>= 1.0.2, < 2.0) 15 | cocoapods-core (= 1.5.3) 16 | cocoapods-deintegrate (>= 1.0.2, < 2.0) 17 | cocoapods-downloader (>= 1.2.0, < 2.0) 18 | cocoapods-plugins (>= 1.0.0, < 2.0) 19 | cocoapods-search (>= 1.0.0, < 2.0) 20 | cocoapods-stats (>= 1.0.0, < 2.0) 21 | cocoapods-trunk (>= 1.3.0, < 2.0) 22 | cocoapods-try (>= 1.1.0, < 2.0) 23 | colored2 (~> 3.1) 24 | escape (~> 0.0.4) 25 | fourflusher (~> 2.0.1) 26 | gh_inspector (~> 1.0) 27 | molinillo (~> 0.6.5) 28 | nap (~> 1.0) 29 | ruby-macho (~> 1.1) 30 | xcodeproj (>= 1.5.7, < 2.0) 31 | cocoapods-core (1.5.3) 32 | activesupport (>= 4.0.2, < 6) 33 | fuzzy_match (~> 2.0.4) 34 | nap (~> 1.0) 35 | cocoapods-deintegrate (1.0.2) 36 | cocoapods-downloader (1.2.1) 37 | cocoapods-plugins (1.0.0) 38 | nap 39 | cocoapods-search (1.0.0) 40 | cocoapods-stats (1.0.0) 41 | cocoapods-trunk (1.3.0) 42 | nap (>= 0.8, < 2.0) 43 | netrc (~> 0.11) 44 | cocoapods-try (1.1.0) 45 | colored2 (3.1.2) 46 | concurrent-ruby (1.0.5) 47 | escape (0.0.4) 48 | fourflusher (2.0.1) 49 | fuzzy_match (2.0.4) 50 | gh_inspector (1.1.3) 51 | i18n (0.9.5) 52 | concurrent-ruby (~> 1.0) 53 | minitest (5.11.3) 54 | molinillo (0.6.5) 55 | nanaimo (0.2.5) 56 | nap (1.1.0) 57 | netrc (0.11.0) 58 | plist (3.4.0) 59 | rouge (2.0.7) 60 | ruby-macho (1.1.0) 61 | thread_safe (0.3.6) 62 | tzinfo (1.2.5) 63 | thread_safe (~> 0.1) 64 | xcodeproj (1.5.9) 65 | CFPropertyList (>= 2.3.3, < 4.0) 66 | atomos (~> 0.1.2) 67 | claide (>= 1.0.2, < 2.0) 68 | colored2 (~> 3.1) 69 | nanaimo (~> 0.2.5) 70 | xcpretty (0.2.8) 71 | rouge (~> 2.0.7) 72 | 73 | PLATFORMS 74 | ruby 75 | 76 | DEPENDENCIES 77 | cocoapods 78 | plist 79 | xcpretty 80 | 81 | BUNDLED WITH 82 | 1.16.2 83 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/CFPropertyList-3.0.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/CFPropertyList-3.0.0.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/activesupport-4.2.10.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/activesupport-4.2.10.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/atomos-0.1.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/atomos-0.1.2.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/claide-1.0.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/claide-1.0.2.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-1.5.3.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-1.5.3.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-core-1.5.3.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-core-1.5.3.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-deintegrate-1.0.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-deintegrate-1.0.2.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-downloader-1.2.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-downloader-1.2.1.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-plugins-1.0.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-plugins-1.0.0.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-search-1.0.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-search-1.0.0.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-stats-1.0.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-stats-1.0.0.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-trunk-1.3.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-trunk-1.3.0.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-try-1.1.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/cocoapods-try-1.1.0.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/colored2-3.1.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/colored2-3.1.2.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/concurrent-ruby-1.0.5.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/concurrent-ruby-1.0.5.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/escape-0.0.4.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/escape-0.0.4.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/fourflusher-2.0.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/fourflusher-2.0.1.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/fuzzy_match-2.0.4.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/fuzzy_match-2.0.4.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/gh_inspector-1.1.3.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/gh_inspector-1.1.3.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/i18n-0.9.5.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/i18n-0.9.5.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/minitest-5.11.3.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/minitest-5.11.3.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/molinillo-0.6.5.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/molinillo-0.6.5.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/nanaimo-0.2.5.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/nanaimo-0.2.5.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/nap-1.1.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/nap-1.1.0.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/netrc-0.11.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/netrc-0.11.0.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/plist-3.4.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/plist-3.4.0.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/rouge-2.0.7.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/rouge-2.0.7.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/ruby-macho-1.1.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/ruby-macho-1.1.0.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/thread_safe-0.3.6.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/thread_safe-0.3.6.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/tzinfo-1.2.5.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/tzinfo-1.2.5.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/xcodeproj-1.5.9.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/xcodeproj-1.5.9.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/GemEnv/vendor/cache/xcpretty-0.2.8.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/GemEnv/vendor/cache/xcpretty-0.2.8.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/RubyEnv/bundler-1.16.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/RubyEnv/bundler-1.16.2.gem -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/RubyEnv/rubygems-2.7.7.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Core/Task/Prepare/Script/RubyEnv/rubygems-2.7.7.zip -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/analyze_scheme.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'xcodeproj' 4 | require 'json' 5 | 6 | scheme = Xcodeproj::XCScheme.new(ARGV[0]) 7 | 8 | targets = {} 9 | prefix = "container:" 10 | scheme.build_action.entries.select { |entry| entry.build_for_archiving? }.map {|entry| entry.buildable_references[0]}.each do |r| 11 | path = r.target_referenced_container 12 | path = path[prefix.length..-1] if path.start_with?(prefix) 13 | targets[r.target_name] = path 14 | end 15 | 16 | puts targets.to_json 17 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/clean_invalid_cert.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | #!/usr/bin/ruby 3 | 4 | INVALID=[] 5 | `/usr/bin/security find-identity -p codesigning`.split("\n").each do |line| 6 | puts line 7 | match = line.match(/\s+\d+\) (\h+) "(.*)" \((.+)\)$/) 8 | next if match.nil? 9 | next if INVALID.include?(match[1]) 10 | INVALID << match 11 | end 12 | 13 | puts "\n Clean invalid identities:" 14 | maxlength = "#{INVALID.length}".length 15 | INVALID.each_with_index do |match, index| 16 | space_count = maxlength - "#{index+1}".length + 1 17 | puts "#{" " * space_count}#{index + 1}) #{match[1]} \"#{match[2]}\": #{match[3]}" 18 | `/usr/bin/security delete-identity -Z #{match[1]} -t` 19 | end 20 | puts " #{" " * maxlength} #{INVALID.length} invalid identities cleaned up" 21 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/clean_invalid_profile.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | #!/usr/bin/ruby 3 | 4 | require './list_provisioning_profiles.rb' 5 | 6 | INVALID = [] 7 | 8 | #COUNT = 0 9 | #def remove(profile, msg) 10 | # COUNT += 1 11 | # puts "#{File.basename(profile.path)} \"#{profile.name}\": #{msg}" 12 | ## File.delete(profile.path) 13 | # true 14 | #end 15 | 16 | local_profiles = get_profiles 17 | 18 | local_profiles.each do |team_id, profiles| 19 | valid_bundle_id = [] 20 | profiles.each do |profile| 21 | if profile.certs.length == 0 22 | INVALID << [profile, "未发现任何有效证书被安装"] 23 | elsif profile.expired? 24 | INVALID << [profile, "已过期"] 25 | elsif valid_bundle_id.include?(profile.bundle_id) 26 | INVALID << [profile, "存在更新的版本"] 27 | else 28 | valid_bundle_id << profile.bundle_id 29 | end 30 | end 31 | end 32 | 33 | INVALID.group_by { |profile, msg| msg }.each do |msg, infos| 34 | puts "#{msg}:" 35 | infos.each do |profile, msg| 36 | puts " #{File.basename(profile.path)} #{profile.name}" 37 | File.delete(profile.path) 38 | end 39 | end 40 | 41 | puts "扫描无效描述文件完成!已清理 #{INVALID.length} 个。" 42 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/find_highest_version_in_repo.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'cocoapods-core' 4 | 5 | repos = ENV['HOME'] +"/.cocoapods/repos/*" 6 | Dir[repos].each do |repo| 7 | source = Pod::Source.new(repo) 8 | version = source.set(ARGV[0]).highest_version 9 | unless version.nil? 10 | puts version.to_s 11 | break 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/install_p12.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | P12_PATH=$1 6 | P12_PASS=$2 7 | 8 | # 创建专属钥匙串 9 | security create-keychain -p "$CODESIGN_KEYCHAIN_PASSWORD" "$CODESIGN_KEYCHAIN" 1>/dev/null 2>&1 10 | 11 | # 设置钥匙串搜索优先级 12 | security list-keychains -d user -s "$CODESIGN_KEYCHAIN" login.keychain 1>/dev/null 2>&1 13 | 14 | # 解锁钥匙串 15 | security unlock-keychain -p "$CODESIGN_KEYCHAIN_PASSWORD" "$CODESIGN_KEYCHAIN" 16 | 17 | # 导入 P12 文件到钥匙串 18 | security import "$P12_PATH" -k "$CODESIGN_KEYCHAIN" -P "$P12_PASS" -T /usr/bin/codesign 1>/dev/null 19 | 20 | # 设置超时时长 21 | #security set-keychain-settings -lut 7200 "$KEYCHAIN" 22 | 23 | # 防止签名弹出“允许访问钥匙串”对话框 24 | security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$CODESIGN_KEYCHAIN_PASSWORD" "$CODESIGN_KEYCHAIN" 1>/dev/null 2>&1 25 | 26 | # 获取 P12 SHA1 指纹码 27 | openssl pkcs12 -in "$P12_PATH" -passin pass:$P12_PASS -nodes 2>/dev/null| openssl x509 -noout -fingerprint 2>/dev/null 28 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/setup_gem_env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exe() { 4 | echo "> $@" 5 | "$@" 6 | 7 | if [[ $? != 0 ]]; then 8 | exit 1 9 | fi 10 | } 11 | 12 | BUNDLEDIR="$(dirname "$BUNDLE_GEMFILE")" 13 | GEMENVDIR="./GemEnv" 14 | 15 | mkdir -p "$BUNDLEDIR" 16 | exe rsync -av "$GEMENVDIR"/ "$BUNDLEDIR" 17 | 18 | check=$(bundle check) 19 | if [[ $? != 0 ]]; then 20 | exe bundle install --local 21 | fi 22 | 23 | exe bundle clean 24 | -------------------------------------------------------------------------------- /Core/Task/Prepare/Script/setup_ruby_env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exe() { 4 | echo "> $@" 5 | "$@" 6 | 7 | if [[ $? != 0 ]]; then 8 | exit 1 9 | fi 10 | } 11 | 12 | UPDATEGEM="" 13 | UPDATEBUNDLER="" 14 | 15 | while [[ $# > 1 ]] 16 | do 17 | key="$1" 18 | 19 | case $key in 20 | -gem) 21 | UPDATEGEM=$2 22 | shift # past argument 23 | ;; 24 | -bundler) 25 | UPDATEBUNDLER=$2 26 | shift # past argument 27 | ;; 28 | *) 29 | # unknown option 30 | ;; 31 | esac 32 | shift # past argument or value 33 | done 34 | 35 | binPath="" 36 | if [[ `id -u` -eq 0 ]] ; then 37 | binPath="-n /usr/local/bin" 38 | fi 39 | 40 | RubyEnv="$(pwd)/RubyEnv" 41 | 42 | if [[ $UPDATEGEM != "" ]]; then 43 | cache=/Library/Caches/com.taobao.Package 44 | rm -rf $cache 45 | mkdir -p $cache 46 | exe cd "$RubyEnv" 47 | exe unzip -q "./rubygems-$UPDATEGEM.zip" -d $cache 48 | exe cd "$cache/rubygems-$UPDATEGEM" 49 | exe ruby setup.rb --no-document ri,rdoc --previous-version=$UPDATEGEM 50 | rm -rf $cache 51 | fi 52 | 53 | if [[ $UPDATEBUNDLER != "" ]]; then 54 | exe cd "$RubyEnv" 55 | exe gem install -l "./bundler-$UPDATEBUNDLER.gem" --no-document $binPath 56 | exe gem clean bundler 57 | fi 58 | -------------------------------------------------------------------------------- /Core/Task/Release/Adhoc.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | items 6 | 7 | 8 | assets 9 | 10 | 11 | kind 12 | software-package 13 | url 14 | IPA URL 15 | 16 | 17 | kind 18 | display-image 19 | url 20 | youdontneedavalidlinkhere.png 21 | 22 | 23 | kind 24 | full-size-image 25 | url 26 | youdontneedavalidlinkhere.png 27 | 28 | 29 | metadata 30 | 31 | bundle-identifier 32 | BUNDLE ID 33 | bundle-version 34 | BUNDLE VERSION 35 | kind 36 | software 37 | title 38 | APP TITLE 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Core/Task/Release/MYPackageCreateTagTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCreateTagTask.h 3 | // 4 | // 5 | // Created by Whirlwind on 15/9/9. 6 | // 7 | // 8 | 9 | #import "MYPackageShellTask.h" 10 | 11 | @interface MYPackageCreateTagTask : MYPackageShellTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Release/MYPackageCreateTagTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCreateTagTask.m 3 | // 4 | // 5 | // Created by Whirlwind on 15/9/9. 6 | // 7 | // 8 | 9 | #import "MYPackageCreateTagTask.h" 10 | 11 | @implementation MYPackageCreateTagTask 12 | 13 | - (NSString *)name { 14 | return @"创建 Tag"; 15 | } 16 | 17 | - (NSString *)version { 18 | return self.config.version; 19 | } 20 | 21 | - (BOOL)launch { 22 | if (![super launch]) { 23 | return NO; 24 | } 25 | self.workingDirectory = self.config.selectedScheme.container ?: self.config.workspace.path; 26 | if ([self executeCommand:[NSString stringWithFormat:@"git tag -f %@", self.version]] != 0) { 27 | self.errorMessage = @"创建 Tag 出错!"; 28 | return NO; 29 | } 30 | if ([self executeCommand:[NSString stringWithFormat:@"git push -f origin %@", self.version]] != 0) { 31 | self.errorMessage = @"提交 Tag 到远程服务器出错!"; 32 | return NO; 33 | } 34 | return YES; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Core/Task/Release/MYPackageUploadLocalTask.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageUploadLocalTask.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/5/19. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBaseTask.h" 10 | 11 | @interface MYPackageUploadLocalTask : MYPackageBaseTask 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Core/Task/Release/MYPackageUploadLocalTask.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageUploadLocalTask.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/5/19. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageUploadLocalTask.h" 10 | #import "MYPackageServer.h" 11 | 12 | @implementation MYPackageUploadLocalTask 13 | 14 | - (NSString *)name { 15 | return @"上传文件到本地服务器"; 16 | } 17 | 18 | - (BOOL)launch { 19 | if (![super launch]) { 20 | return NO; 21 | } 22 | 23 | NSError *error; 24 | if (![MYPackageServer publishLocalFolder:self.config.productsDir serverPath:self.config.serverPath error:&error]) { 25 | self.errorMessage = [error localizedDescription]; 26 | return NO; 27 | } 28 | self.config.downloadUrl = [[MYPackageServer sharedInstance] downloadUrlForPath:self.config.serverPath]; 29 | return YES; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Core/Task/Release/index.html: -------------------------------------------------------------------------------- 1 | 3 | click this link to install 4 | 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | 2 | source 'https://rubygems.org' 3 | 4 | gem 'cocoapods' 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.0) 5 | activesupport (4.2.10) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | atomos (0.1.2) 11 | claide (1.0.2) 12 | cocoapods (1.5.3) 13 | activesupport (>= 4.0.2, < 5) 14 | claide (>= 1.0.2, < 2.0) 15 | cocoapods-core (= 1.5.3) 16 | cocoapods-deintegrate (>= 1.0.2, < 2.0) 17 | cocoapods-downloader (>= 1.2.0, < 2.0) 18 | cocoapods-plugins (>= 1.0.0, < 2.0) 19 | cocoapods-search (>= 1.0.0, < 2.0) 20 | cocoapods-stats (>= 1.0.0, < 2.0) 21 | cocoapods-trunk (>= 1.3.0, < 2.0) 22 | cocoapods-try (>= 1.1.0, < 2.0) 23 | colored2 (~> 3.1) 24 | escape (~> 0.0.4) 25 | fourflusher (~> 2.0.1) 26 | gh_inspector (~> 1.0) 27 | molinillo (~> 0.6.5) 28 | nap (~> 1.0) 29 | ruby-macho (~> 1.1) 30 | xcodeproj (>= 1.5.7, < 2.0) 31 | cocoapods-core (1.5.3) 32 | activesupport (>= 4.0.2, < 6) 33 | fuzzy_match (~> 2.0.4) 34 | nap (~> 1.0) 35 | cocoapods-deintegrate (1.0.2) 36 | cocoapods-downloader (1.2.1) 37 | cocoapods-plugins (1.0.0) 38 | nap 39 | cocoapods-search (1.0.0) 40 | cocoapods-stats (1.0.0) 41 | cocoapods-trunk (1.3.0) 42 | nap (>= 0.8, < 2.0) 43 | netrc (~> 0.11) 44 | cocoapods-try (1.1.0) 45 | colored2 (3.1.2) 46 | concurrent-ruby (1.0.5) 47 | escape (0.0.4) 48 | fourflusher (2.0.1) 49 | fuzzy_match (2.0.4) 50 | gh_inspector (1.1.3) 51 | i18n (0.9.5) 52 | concurrent-ruby (~> 1.0) 53 | minitest (5.11.3) 54 | molinillo (0.6.5) 55 | nanaimo (0.2.5) 56 | nap (1.1.0) 57 | netrc (0.11.0) 58 | ruby-macho (1.1.0) 59 | thread_safe (0.3.6) 60 | tzinfo (1.2.5) 61 | thread_safe (~> 0.1) 62 | xcodeproj (1.5.9) 63 | CFPropertyList (>= 2.3.3, < 4.0) 64 | atomos (~> 0.1.2) 65 | claide (>= 1.0.2, < 2.0) 66 | colored2 (~> 3.1) 67 | nanaimo (~> 0.2.5) 68 | 69 | PLATFORMS 70 | ruby 71 | 72 | DEPENDENCIES 73 | cocoapods 74 | 75 | BUNDLED WITH 76 | 1.14.6 77 | -------------------------------------------------------------------------------- /Package.xcodeproj/xcshareddata/xcschemes/Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Package/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Package/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Package/Assets.xcassets/cb_mono_off.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cb_mono_off.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "cb_mono_off@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Package/Assets.xcassets/cb_mono_off.imageset/cb_mono_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Package/Assets.xcassets/cb_mono_off.imageset/cb_mono_off.png -------------------------------------------------------------------------------- /Package/Assets.xcassets/cb_mono_off.imageset/cb_mono_off@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Package/Assets.xcassets/cb_mono_off.imageset/cb_mono_off@2x.png -------------------------------------------------------------------------------- /Package/Assets.xcassets/cb_mono_on.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cb_mono_on.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "cb_mono_on@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Package/Assets.xcassets/cb_mono_on.imageset/cb_mono_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Package/Assets.xcassets/cb_mono_on.imageset/cb_mono_on.png -------------------------------------------------------------------------------- /Package/Assets.xcassets/cb_mono_on.imageset/cb_mono_on@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dijkst/XcodePackage/77b30feb8c5f238ea421f3ac70e4c9ce17f5e4d7/Package/Assets.xcassets/cb_mono_on.imageset/cb_mono_on@2x.png -------------------------------------------------------------------------------- /Package/CommandLine/MYPackageCommandLine.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCommandLine.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/10/22. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | -workspace 13 | -project 14 | -target 15 | -task 16 | */ 17 | @interface MYPackageCommandLine : NSObject 18 | 19 | + (BOOL)canRunWithCommandLineMode; 20 | + (int)run; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Package/CommandLine/MYPackageCommandLine.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageCommandLine.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/10/22. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageCommandLine.h" 10 | #import "MYPackageTaskManager.h" 11 | #import "MYPackageTaskManager+TaskList.h" 12 | 13 | #if __has_include() 14 | # import 15 | #else 16 | # import "ObjCShell.h" 17 | #endif 18 | 19 | @implementation MYPackageCommandLine 20 | 21 | + (BOOL)canRunWithCommandLineMode { 22 | NSArray *args = [[NSProcessInfo processInfo] arguments]; 23 | for (NSString *arg in @[@"-project", @"-workspace", @"setup", @"export"]) { 24 | if ([args containsObject:arg]) { 25 | [ObjCShell setIsCMDEnvironment:YES]; 26 | break; 27 | } 28 | } 29 | return [ObjCShell isCMDEnvironment]; 30 | } 31 | 32 | + (MYPackageConfig *)createConfig { 33 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 34 | 35 | MYPackageConfig *config = [[MYPackageConfig alloc] init]; 36 | 37 | // for all 38 | config.workspaceFilePath = [self getProjectPath:[defaults stringForKey:@"workspace"] ? : [defaults stringForKey:@"project"] ]; 39 | config.selectedSchemeName = [defaults stringForKey:@"scheme"]; 40 | config.name = [defaults stringForKey:@"name"]; 41 | config.bundleId = [defaults stringForKey:@"bundle-id"]; 42 | config.version = [defaults stringForKey:@"version"]; 43 | config.configruation = [defaults stringForKey:@"configuration"]; 44 | config.xcconfigSettings = [defaults stringForKey:@"xcconfig"]; 45 | config.outputDir = [defaults stringForKey:@"output"]; 46 | 47 | // for pod 48 | config.authorName = [defaults stringForKey:@"author-name"]; 49 | config.authorEmail = [defaults stringForKey:@"author-email"]; 50 | 51 | // for app 52 | config.archivePath = [defaults stringForKey:@"archive-path"]; 53 | config.teamID = [defaults stringForKey:@"team-id"]; 54 | config.signType = [defaults stringForKey:@"sign-type"]; 55 | 56 | return config; 57 | } 58 | 59 | + (int)run { 60 | NSArray *args = [[NSProcessInfo processInfo] arguments]; 61 | MYPackageConfig *config = [self createConfig]; 62 | 63 | MYPackageTaskManager *manager = [[MYPackageTaskManager alloc] init]; 64 | manager.config = config; 65 | 66 | [config.logger logN:@"从命令行模式启动"]; 67 | 68 | BOOL result; 69 | if ([args containsObject:@"setup"]) { 70 | result = [manager runTaskClassNames:taskClassOrderSetup]; 71 | } else if ([args containsObject:@"export"]) { 72 | result = [manager runTaskClassNames:taskClassOrderExportIPA]; 73 | } else { 74 | NSMutableArray *tasks = [taskClassOrderPrefix mutableCopy]; 75 | if ([args containsObject:@"-no-remote-git"]) { 76 | [tasks removeObject:@"MYPackageCheckGitTask"]; 77 | } 78 | result = [manager runTaskClassNames:tasks]; 79 | if (!result) { 80 | return 1; 81 | } 82 | 83 | if (config.appTarget) { 84 | tasks = [taskClassOrderForApp mutableCopy]; 85 | if ([config.teamID length] == 0 || [config.signType length] == 0) { 86 | [tasks removeObject:@"MYPackageResignAppTask"]; 87 | } 88 | } else { 89 | tasks = [taskClassOrderForLib mutableCopy]; 90 | } 91 | if ([args containsObject:@"-no-remote-git"]) { 92 | [tasks removeObject:@"MYPackageCreateTagTask"]; 93 | } 94 | result = [manager runTaskClassNames:tasks]; 95 | if (!result) { 96 | return 1; 97 | } 98 | 99 | result = [manager runTaskClassNames:taskClassOrderSuffix]; 100 | } 101 | return result ? 0 : 1; 102 | } 103 | 104 | + (NSString *)getProjectPath:(NSString *)filePath { 105 | NSArray *extensions = @[@".xcworkspace", @".xcodeproj"]; 106 | for (NSString *ext in extensions) { 107 | if ([[filePath lowercaseString] hasSuffix:ext]) { 108 | return filePath; 109 | } 110 | } 111 | 112 | NSFileManager *fm = [NSFileManager defaultManager]; 113 | for (NSString *ext in extensions) { 114 | NSString *path = [filePath stringByAppendingString:ext]; 115 | if ([fm fileExistsAtPath:path]) { 116 | return path; 117 | } 118 | } 119 | 120 | BOOL isDirectory = YES; 121 | if (![fm fileExistsAtPath:filePath isDirectory:&isDirectory] || !isDirectory) { 122 | return filePath; 123 | } 124 | 125 | NSArray *contents = [fm contentsOfDirectoryAtPath:filePath error:nil]; 126 | if ([contents count] == 0) { 127 | return filePath; 128 | } 129 | 130 | for (NSString *ext in extensions) { 131 | NSArray *xcworkspaces = [contents filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF ENDSWITH[c] %@", ext]]; 132 | if ([xcworkspaces count] > 0) { 133 | return [filePath stringByAppendingPathComponent:xcworkspaces[0]]; 134 | } 135 | } 136 | return filePath; 137 | } 138 | 139 | @end 140 | -------------------------------------------------------------------------------- /Package/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | zh_CN 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 2.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | NSHumanReadableCopyright 33 | Copyright © 2015年 taobao. All rights reserved. 34 | NSMainNibFile 35 | MainMenu 36 | NSPrincipalClass 37 | NSApplication 38 | 39 | 40 | -------------------------------------------------------------------------------- /Package/PackageAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // PackageAppDelegate.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/10/21. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface PackageAppDelegate : NSObject 12 | 13 | @property (weak) IBOutlet NSWindow *window; 14 | 15 | - (IBAction)showLogWindow:(id)sender; 16 | - (IBAction)showFinder:(id)sender; 17 | 18 | @end 19 | 20 | -------------------------------------------------------------------------------- /Package/PackageAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // PackageAppDelegate.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/10/21. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "PackageAppDelegate.h" 10 | #import "SafeThread.h" 11 | #import "MYPackageContainerViewController.h" 12 | #import "MYPackageConfig.h" 13 | 14 | #import "MYPackageCleanInvalidCertTask.h" 15 | #import "MYPackageCleanInvalidProfileTask.h" 16 | 17 | @interface PackageAppDelegate () 18 | 19 | @property (nonatomic, strong) MYPackageContainerViewController *mainVC; 20 | 21 | @end 22 | 23 | @implementation PackageAppDelegate 24 | 25 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 26 | _mainVC = [[MYPackageContainerViewController alloc] init]; 27 | [_mainVC setConfig:[[MYPackageConfig alloc] init]]; 28 | 29 | NSUserDefaults *args = [NSUserDefaults standardUserDefaults]; 30 | _mainVC.config.workspaceFilePath = [args objectForKey:@"workspace"] ?: [args objectForKey:@"project"]; 31 | 32 | self.window.contentViewController = _mainVC; 33 | self.window.titleVisibility = NSWindowTitleHidden; 34 | self.window.titlebarAppearsTransparent = YES; 35 | self.window.styleMask |= NSFullSizeContentViewWindowMask; 36 | self.window.movableByWindowBackground = YES; 37 | } 38 | 39 | - (void)windowWillClose:(NSNotification *)notification { 40 | } 41 | 42 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 43 | // Insert code here to tear down your application 44 | } 45 | 46 | - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication { 47 | return YES; 48 | } 49 | 50 | - (void)postMessageNotification:(NSString *)message { 51 | dispatch_main_async_safe(^{ 52 | NSUserNotification *notification = [[NSUserNotification alloc] init]; 53 | notification.soundName = NSUserNotificationDefaultSoundName; 54 | notification.informativeText = message; 55 | [NSUserNotificationCenter defaultUserNotificationCenter].delegate = self; 56 | [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; 57 | }); 58 | } 59 | 60 | - (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center 61 | shouldPresentNotification:(NSUserNotification *)notification { 62 | return YES; 63 | } 64 | 65 | #pragma mark - window 66 | 67 | - (IBAction)showLogWindow:(id)sender { 68 | NSURL *url = [NSURL fileURLWithPath:_mainVC.config.logPath]; 69 | [[NSWorkspace sharedWorkspace] openURL:url]; 70 | } 71 | 72 | - (IBAction)showFinder:(id)sender { 73 | [[NSWorkspace sharedWorkspace] openFile:_mainVC.config.outputDir]; 74 | } 75 | 76 | - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { 77 | if (menuItem.tag == 101) { 78 | // 打开输出目录菜单只在选择了项目之后才有效 79 | return _mainVC.config.workspace.path != nil; 80 | } 81 | return YES; 82 | } 83 | 84 | - (IBAction)cleanInvalidProfileAndCert:(id)sender { 85 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 86 | MYPackageTaskManager *taskManager = [[MYPackageTaskManager alloc] init]; 87 | [taskManager setConfig:self.mainVC.config]; 88 | BOOL result = [taskManager runTaskClassNames:@[NSStringFromClass([MYPackageCleanInvalidCertTask class]), NSStringFromClass([MYPackageCleanInvalidProfileTask class])]]; 89 | dispatch_main_async_safe(^{ 90 | NSUserNotification *notification = [[NSUserNotification alloc] init]; 91 | notification.informativeText = [NSString stringWithFormat:@"清理无效证书和描述文件执行%@", result ? @"成功" : @"失败"]; 92 | notification.soundName = NSUserNotificationDefaultSoundName; 93 | [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self]; 94 | [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; 95 | }); 96 | }); 97 | } 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /Package/PrefixHeader.pch: -------------------------------------------------------------------------------- 1 | // 2 | // PrefixHeader.pch 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/3/15. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #ifndef PrefixHeader_pch 10 | #define PrefixHeader_pch 11 | 12 | #define PathIsWorkspace(path) [[path pathExtension] isEqualToString:@"xcworkspace"] 13 | #define PathIsProject(path) [[path pathExtension] isEqualToString:@"xcodeproj"] 14 | 15 | #endif /* PrefixHeader_pch */ 16 | -------------------------------------------------------------------------------- /Package/Server/Cert/create_ca_root_cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | openssl genrsa -out myCA.key 2048 4 | openssl req -x509 -sha256 -new -key myCA.key -out myCA.cer -days 3365 -subj /CN=PackageCA 5 | -------------------------------------------------------------------------------- /Package/Server/Cert/create_self_sign_cert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | IP=$1 4 | 5 | pushd $(dirname "${0}") > /dev/null 6 | basedir=$(pwd -L) 7 | # Use "pwd -P" for the path without links. man bash for more info. 8 | popd > /dev/null 9 | 10 | set -x 11 | 12 | openssl genrsa -out server.key 2048 13 | openssl req -new -out mycert1.req -key server.key -subj /CN=${IP} 14 | openssl x509 -req -sha256 -in mycert1.req -out server.cer -CAkey "${basedir}/myCA.key" -CA "${basedir}/myCA.cer" -days 3365 -CAcreateserial 15 | rm -rf mycert1.req 16 | -------------------------------------------------------------------------------- /Package/Server/Cert/myCA.cer: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC+zCCAeOgAwIBAgIJAIR8vCadIaL3MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV 3 | BAMMCVBhY2thZ2VDQTAeFw0xNzAyMTUwNzExMTFaFw0xOTAyMTUwNzExMTFaMBQx 4 | EjAQBgNVBAMMCVBhY2thZ2VDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 5 | ggEBALxUOmu9iBbPe0uYETY+dFLcUcj5MJBwxi/xz2PW7Lcoyl37B672vsZSdFyh 6 | rcapJf3FPmcIH9GAlyZLoqQfc2rAnvJv3r409XnvE5BZKmawH7wJdsg92RJFNnI5 7 | NV2r5HsSUvVOuk8JfPeko+Ty+aiLxOPnU3POOeF0hB/tCmPNj1FZBvebEq5czJ1X 8 | LLCAiGxpUsRBAoQVgcE3uxm3SD8bYiVq6sSAamH1yPmlFjWgP/e+hVWlfvc5Qj44 9 | h85UIoPUeKSb27rGQcmKVZ7hTbxYK9TGzsgPNhxOeSnF6ztt/xzkiWE7zdeWTqlh 10 | Y8lNr1pK4jAQ65w4MdFv2B6qqXcCAwEAAaNQME4wHQYDVR0OBBYEFFatYwyySjvs 11 | 7pEwQCtyRccI68ABMB8GA1UdIwQYMBaAFFatYwyySjvs7pEwQCtyRccI68ABMAwG 12 | A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFQxKznJoDeXWy/cX0wu/PYl 13 | LKWR7n/lN8DGvwlGI0NHamLywFZehA7kLNHgP+1S39Nd+tUvFWKPKCubOq+xJKoG 14 | Fc/jX40i0mgMoTtc6cgw0I/CHqfUGgJ6AP0ZNj7eKsKB+6tTmTE201dbYZnCj1Da 15 | 8TfiioZ39RlNIIc9sx1h9ZCM//ApMmyR25bOKSNjKxKX7VWBlFXNcpCmle48XJ1g 16 | C9CxXHcVUo+8+ZDMezUUfPhQwMdlCMNApuin9Qt5cEM2JyR9sreONwYRy8cgSf+7 17 | nhNU15PPUOeSbh0GPbAhThJEZVCzKp84N58jvOswcxozHU5hkQ7Q2og/fPDOwiI= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /Package/Server/Cert/myCA.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAvFQ6a72IFs97S5gRNj50UtxRyPkwkHDGL/HPY9bstyjKXfsH 3 | rva+xlJ0XKGtxqkl/cU+Zwgf0YCXJkuipB9zasCe8m/evjT1ee8TkFkqZrAfvAl2 4 | yD3ZEkU2cjk1XavkexJS9U66Twl896Sj5PL5qIvE4+dTc8454XSEH+0KY82PUVkG 5 | 95sSrlzMnVcssICIbGlSxEEChBWBwTe7GbdIPxtiJWrqxIBqYfXI+aUWNaA/976F 6 | VaV+9zlCPjiHzlQig9R4pJvbusZByYpVnuFNvFgr1MbOyA82HE55KcXrO23/HOSJ 7 | YTvN15ZOqWFjyU2vWkriMBDrnDgx0W/YHqqpdwIDAQABAoIBAQCPCaoViq6CJLQQ 8 | hj8MZAMVrCCx9BHeosjkQVSzD9pagrCxEKiVSvM6zgVzFZlW+cHRLK5ostL33CLx 9 | AyFUZk7TFdmjaTRUYwTOx7akPBToSXzQ5J9DFzZ3mfnwZ2MQ8i1nDFPV3SXEQgCY 10 | VoPksGbOjNnN/zRzEH8FrelsFi19/qS2FykKCIL57+GWAinDm+W6ndZkS39odjOx 11 | mZuWUyGsyGUSTpOwvoJw8hBMZBQQjClFKMCOfM3lK8rklIVnMaJ+O0jNeIQEDsLs 12 | 3FUJCH/myChGU1xkX4Dp+wrX2ymy5W81VSU5dnoOa8c5FIKtcnqJ9ylrn6FqQP3b 13 | itjVaVGBAoGBAOT8QeY8Ixezo9LJUEzNvY/UJ9lGfYHEVwV9fFMBBEhhr0t2B63E 14 | KEriYwDNi2R5PYCANe1SduQXUs7xiYhuQ0sLhBnLTniUOtIbDaFpPbkEUasby910 15 | by/jf4oEarOffIn8kLGtGdFLDrHDUz7VNbHs9Sg4tPZzqojLKl/D0RwFAoGBANKM 16 | FJTV4BdGKqO5WJaeg+PTEuTd8wv2BpcM8RLDdGtAhzv9NsWFjukwzMn9lbNwrdO0 17 | AV04mdh/p6nv8oFUwYxCQwDplrLn6KIlpRkRXMRJDxDRXDGi+PN+XPCHqQ3mFvhn 18 | UfpatQSrmtbjHWO6y1aQYY7/S27P/5acEwEzF+RLAoGASrDhyfi5taR5/mp+D99Y 19 | Qs8ABIZSr/5cKEVLRUPior2CUUn5rTt7rRl1E7D4E9jcL8siS/wHHdVhxHDS3Lai 20 | qpriBwvi17/6A8ihRJIweOYowXh6ogy1q9g4JMvWBisyk0vX4qzE7BjgfSlqQOeI 21 | 8TWKrgj5Nh56dM5YTw5vagUCgYEAn/lFTgzvNl/e2AVk9RQ9++WNbo3hNI5bVXvE 22 | 08/kbfVai08KxauD+VRV1YeyreOIf9k+TBlMc34S44DoDjJa2PojtNbG5OpmDIVz 23 | 2wchKBamts2MMj60zzWM4kkBPwk7HMRMOl+fOIw2mt13oCX2h7AuIBuDsg+iEEpH 24 | +lpa5d8CgYBIYGDr5KEu2IfwWXsVFnJbgjG8ECaxB/GnkdaorJIN4rjNjOCYXYYL 25 | rqIWbUsIJclUAdxycJi1R1MQYrF3PD0W0BB8jJ4zkVhFfyi/u/i6mp9Lr4wAQP5Z 26 | sp0TILp0hyO8EVwbTdtcWnLr6c7rjI1MV2G2dJXqG8iClh0IynYnug== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /Package/Server/MYPackageServer.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageServer.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/5/19. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MYPackageServer : NSObject 12 | 13 | @property (nonatomic, assign) BOOL isRunning; 14 | 15 | + (instancetype)sharedInstance; 16 | 17 | + (NSString *)localDirectory; 18 | - (NSString *)downloadUrlForPath:(NSString *)path; 19 | 20 | - (BOOL)startup; 21 | - (void)stop; 22 | 23 | + (BOOL)publishLocalFolder:(NSString *)path serverPath:(NSString *)serverPath error:(NSError **)error; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /Package/Server/MYPackageServer.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageServer.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 16/5/19. 6 | // Copyright © 2016年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageServer.h" 10 | #import 11 | 12 | #define kServerPort 8086 13 | 14 | @interface MYPackageServer () 15 | 16 | @property (nonatomic, strong) CRHTTPServer *webServer; 17 | @property (nonatomic, assign) BOOL running; 18 | 19 | @end 20 | 21 | @implementation MYPackageServer 22 | 23 | + (instancetype)sharedInstance { 24 | static dispatch_once_t onceToken; 25 | static MYPackageServer *__server; 26 | dispatch_once(&onceToken, ^{ 27 | __server = [[[self class] alloc] init]; 28 | [__server startup]; 29 | }); 30 | return __server; 31 | } 32 | 33 | + (BOOL)publishLocalFolder:(NSString *)path serverPath:(NSString *)serverPath error:(NSError * *)error { 34 | NSFileManager *fm = [NSFileManager defaultManager]; 35 | NSString *fullPath = [[self localDirectory] stringByAppendingPathComponent:serverPath]; 36 | if ([fm fileExistsAtPath:fullPath]) { 37 | [fm removeItemAtPath:fullPath error:nil]; 38 | } 39 | if (![fm createDirectoryAtPath:[fullPath stringByDeletingLastPathComponent] 40 | withIntermediateDirectories:YES 41 | attributes:nil 42 | error:error]) { 43 | return NO; 44 | } 45 | 46 | NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init]; 47 | [dateFormat setDateFormat:@"yyyy-MM-dd_HH-mm-ss"]; 48 | 49 | if (![fm copyItemAtPath:path toPath:fullPath error:error]) { 50 | return NO; 51 | } 52 | return YES; 53 | } 54 | 55 | + (NSString *)localDirectory { 56 | return [[[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] stringByAppendingPathComponent:@"Server"]; 57 | } 58 | 59 | - (CRHTTPServer *)webServer { 60 | if (!_webServer) { 61 | _webServer = [[CRHTTPServer alloc] initWithDelegate:self]; 62 | [_webServer mount:@"/" directoryAtPath:[[self class] localDirectory]]; 63 | } 64 | return _webServer; 65 | } 66 | 67 | - (NSURLComponents *)serverAddress { 68 | NSURLComponents *components = [[NSURLComponents alloc] init]; 69 | // components.scheme = @"https"; 70 | components.scheme = @"http"; 71 | components.port = @(kServerPort); 72 | components.host = [[NSHost hostWithName:[[NSHost currentHost] name]] address]; 73 | return components; 74 | } 75 | 76 | - (NSString *)downloadUrlForPath:(NSString *)path { 77 | NSURLComponents *components = [self serverAddress]; 78 | components.path = [@"/" stringByAppendingPathComponent:path]; 79 | return [[components URL] absoluteString]; 80 | } 81 | 82 | - (BOOL)startup { 83 | if (_running) { 84 | return YES; 85 | } 86 | NSError *error = nil; 87 | if ([self.webServer startListening:&error portNumber:kServerPort]) { 88 | _running = YES; 89 | return YES; 90 | } else { 91 | NSLog(@"start server fail: %@", error); 92 | return NO; 93 | } 94 | } 95 | 96 | - (void)stop { 97 | _running = NO; 98 | [_webServer stopListening]; 99 | } 100 | 101 | #pragma mark - CRServerDelegate 102 | - (void)serverWillStopListening:(CRServer *)server { 103 | _running = NO; 104 | } 105 | 106 | @end 107 | -------------------------------------------------------------------------------- /Package/View/NSView+Enable.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSView+Enable.h 3 | // MYBLELock 4 | // 5 | // Created by Whirlwind on 15/1/13. 6 | // Copyright (c) 2015年 luobo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSView (Enable) 12 | 13 | - (void)setEnabled:(BOOL)enabled bySuperView:(BOOL)bySuperView; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Package/View/NSView+Enable.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSView+Enable.m 3 | // MYBLELock 4 | // 5 | // Created by Whirlwind on 15/1/13. 6 | // Copyright (c) 2015年 luobo. All rights reserved. 7 | // 8 | 9 | #import "NSView+Enable.h" 10 | #import 11 | 12 | @implementation NSView (Enable) 13 | 14 | - (void)setOriginEnabled:(BOOL)enabled { 15 | objc_setAssociatedObject(self, @selector(setOriginEnabled:), @(enabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 16 | } 17 | 18 | - (BOOL)originEnabled { 19 | NSNumber *v = objc_getAssociatedObject(self, @selector(setOriginEnabled:)); 20 | if (v) { 21 | return [v boolValue]; 22 | } 23 | return YES; 24 | } 25 | 26 | - (void)setEnabled:(BOOL)enabled bySuperView:(BOOL)bySuperView { 27 | if (!bySuperView) { 28 | [self setOriginEnabled:enabled]; 29 | } 30 | enabled = enabled & [self originEnabled]; 31 | if ([self isKindOfClass:[NSControl class]]) { 32 | NSControl *control = (NSControl *)self; 33 | NSNumber *v = objc_getAssociatedObject(self, @selector(setOriginEnabled:)); 34 | if (!v) { 35 | [self setOriginEnabled:control.enabled]; 36 | } 37 | [control setEnabled:enabled]; 38 | } else { 39 | for (NSView *view in self.subviews) { 40 | [view setEnabled:enabled bySuperView:YES]; 41 | } 42 | } 43 | objc_setAssociatedObject(self, @selector(setEnabled:), @(enabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Package/ViewController/Base/MYPackageBaseViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageBaseViewController.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/23. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MYPackageConfig.h" 11 | #import "MYPackageBaseTask.h" 12 | #import "SafeThread.h" 13 | #import "MYPackageTaskManager.h" 14 | 15 | @class MYPackageContainerViewController; 16 | @interface MYPackageBaseViewController : NSViewController 17 | 18 | @property (nonatomic, copy) MYPackageConfig *config; 19 | 20 | @property (nonatomic, strong) MYPackageTaskManager *taskManager; 21 | 22 | @property (nonatomic, assign) BOOL busy; 23 | 24 | @property (nonatomic, weak) MYPackageContainerViewController *containerVC; 25 | 26 | @property (nonatomic, assign) BOOL showBackButton; 27 | 28 | @property (strong, nonatomic) IBOutlet NSButton *rightButton; 29 | 30 | - (void)postUserNotificationWithResult:(BOOL)result; 31 | 32 | - (BOOL)runTasks:(NSArray *)tasks autoOrder:(BOOL)order; 33 | 34 | - (void)stopTask NS_REQUIRES_SUPER; 35 | - (void)startTask NS_REQUIRES_SUPER; 36 | - (NSMutableArray *)tasksForRun NS_REQUIRES_SUPER; 37 | - (void)tasks:(NSArray *)tasks didFinishWithResult:(BOOL)result NS_REQUIRES_SUPER; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Package/ViewController/Base/MYPackageBaseViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageBaseViewController.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/23. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBaseViewController.h" 10 | #import "MYPackageContainerViewController.h" 11 | 12 | @interface MYPackageBaseViewController () 13 | 14 | @end 15 | 16 | @implementation MYPackageBaseViewController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | [self.view setWantsLayer:YES]; 21 | self.showBackButton = YES; 22 | } 23 | 24 | - (void)viewWillDisappear { 25 | [super viewWillDisappear]; 26 | if (self.busy) { 27 | [self.taskManager cancelAllTask]; 28 | } 29 | } 30 | 31 | - (void)setRightButton:(NSButton *)rightButton { 32 | _rightButton = rightButton; 33 | 34 | self.containerVC.rightButton = rightButton; 35 | } 36 | 37 | #pragma mark - task 38 | - (IBAction)startAction:(id)sender { 39 | if (self.busy) { 40 | [self stopTask]; 41 | } else { 42 | [self startTask]; 43 | } 44 | } 45 | 46 | - (void)stopTask { 47 | [self.taskManager cancelAllTask]; 48 | } 49 | 50 | - (void)startTask { 51 | NSMutableArray *tasks = [self tasksForRun]; 52 | if ([tasks count] == 0) { 53 | return; 54 | } 55 | self.rightButton.title = @"取消"; 56 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 57 | BOOL result = [self runTasks:tasks autoOrder:YES]; 58 | dispatch_main_async_safe(^{ 59 | self.rightButton.title = @"开始"; 60 | [self tasks:tasks didFinishWithResult:result]; 61 | }); 62 | }); 63 | } 64 | 65 | - (NSMutableArray *)tasksForRun { 66 | return [NSMutableArray array]; 67 | } 68 | 69 | - (void)tasks:(NSArray *)tasks didFinishWithResult:(BOOL)result { 70 | 71 | } 72 | 73 | - (MYPackageTaskManager *)taskManager { 74 | if (!_taskManager) { 75 | _taskManager = [[MYPackageTaskManager alloc] init]; 76 | [_taskManager setConfig:self.config]; 77 | } 78 | return _taskManager; 79 | } 80 | 81 | - (BOOL)runTasks:(NSArray *)tasks autoOrder:(BOOL)order { 82 | self.busy = YES; 83 | BOOL status = order ? [self.taskManager runTaskClassNamesInOrder:tasks] : [self.taskManager runTaskClassNames:tasks]; 84 | NSString *errorMessage = self.taskManager.lastErrorMessage; 85 | [self.containerVC finishLoadingWithErrorText:errorMessage]; 86 | self.busy = NO; 87 | return status; 88 | } 89 | 90 | - (void)setBusy:(BOOL)busy { 91 | _busy = busy; 92 | dispatch_main_async_safe(^{ 93 | [self.containerVC.backButton setHidden:busy || !self.showBackButton || self.containerVC.childViewControllers.count <= 1]; 94 | }); 95 | } 96 | 97 | #pragma mark - NSUserNotificationCenter 98 | 99 | - (void)postUserNotificationWithResult:(BOOL)result { 100 | dispatch_main_async_safe(^{ 101 | NSUserNotification *notification = [[NSUserNotification alloc] init]; 102 | notification.informativeText = [NSString stringWithFormat:@"打包任务执行%@", result ? @"成功" : @"失败"]; 103 | notification.soundName = NSUserNotificationDefaultSoundName; 104 | [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self]; 105 | [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; 106 | }); 107 | } 108 | 109 | - (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification { 110 | 111 | } 112 | 113 | - (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification { 114 | return YES; 115 | } 116 | 117 | @end 118 | -------------------------------------------------------------------------------- /Package/ViewController/Base/MYPackageDetailLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageDetailLabel.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/27. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | IB_DESIGNABLE 12 | @interface MYPackageDetailLabel : NSTextField 13 | 14 | @property (nonatomic, assign) IBInspectable BOOL autoWidth; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Package/ViewController/Base/MYPackageDetailLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageDetailLabel.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/27. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageDetailLabel.h" 10 | 11 | @implementation MYPackageDetailLabel 12 | 13 | - (instancetype)initWithCoder:(NSCoder *)coder { 14 | if (self = [super initWithCoder:coder]) { 15 | self.autoWidth = NO; 16 | } 17 | return self; 18 | } 19 | - (void)awakeFromNib { 20 | self.cell.font = [NSFont boldSystemFontOfSize:16]; 21 | self.textColor = [NSColor lightGrayColor]; 22 | } 23 | 24 | - (NSSize)intrinsicContentSize { 25 | if (![self.cell wraps] || !self.autoWidth) { 26 | return [super intrinsicContentSize]; 27 | } 28 | NSRect frame = NSMakeRect(0, 0, CGFLOAT_MAX, CGFLOAT_MAX); 29 | return [self.cell cellSizeForBounds:frame]; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Package/ViewController/Base/MYPackageTableRowView.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageTableRowView.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/26. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MYPackageTableRowView : NSTableRowView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Package/ViewController/Base/MYPackageTableRowView.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageTableRowView.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/26. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageTableRowView.h" 10 | 11 | @implementation MYPackageTableRowView 12 | 13 | - (void)drawSelectionInRect:(NSRect)dirtyRect { 14 | if (self.selectionHighlightStyle != NSTableViewSelectionHighlightStyleNone) { 15 | [[NSColor cyanColor] setFill]; 16 | NSBezierPath *selectionPath = [NSBezierPath bezierPathWithRoundedRect:self.bounds xRadius:0 yRadius:0]; 17 | [selectionPath fill]; 18 | } 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Package/ViewController/Base/MYPackageTitleLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageTitleLabel.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/26. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MYPackageTitleLabel : NSTextField 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Package/ViewController/Base/MYPackageTitleLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageTitleLabel.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/26. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageTitleLabel.h" 10 | 11 | @implementation MYPackageTitleLabel 12 | 13 | - (void)awakeFromNib { 14 | self.cell.font = [NSFont boldSystemFontOfSize:25]; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Package/ViewController/BuildIPA/MYPackageBuildAppViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageBuildAppViewController.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/2/8. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBaseViewController.h" 10 | 11 | @interface MYPackageBuildAppViewController : MYPackageBaseViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Package/ViewController/BuildIPA/MYPackageQRCodeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageQRCodeViewController.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/2/13. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MYPackageQRCodeViewController : NSViewController 12 | 13 | @property (weak) IBOutlet NSImageView *imageView; 14 | 15 | @property (nonatomic, strong) NSImage *image; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Package/ViewController/BuildIPA/MYPackageQRCodeViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageQRCodeViewController.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 2017/2/13. 6 | // Copyright © 2017年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageQRCodeViewController.h" 10 | 11 | @interface MYPackageQRCodeViewController () 12 | 13 | @end 14 | 15 | @implementation MYPackageQRCodeViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | [self.imageView setImage:self.image]; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Package/ViewController/BuildIPA/MYPackageQRCodeViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Package/ViewController/Container/MYPackageContainerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageContainerViewController.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/23. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBaseViewController.h" 10 | #import "MYPackageConfig.h" 11 | 12 | @interface MYPackageContainerViewController : MYPackageBaseViewController 13 | 14 | @property (nonatomic, readonly) MYPackageBaseViewController *currentViewController; 15 | 16 | @property (weak) IBOutlet NSButton *backButton; 17 | 18 | - (void)showNormalText:(NSString *)text; 19 | - (void)showHighlightText:(NSString *)text; 20 | 21 | - (void)showLoadingWithText:(NSString *)text; 22 | - (void)finishLoadingWithErrorText:(NSString *)text; 23 | - (void)finishLoading; 24 | 25 | - (void)changeTopViewController:(MYPackageBaseViewController *)vc; 26 | - (void)pushViewController:(MYPackageBaseViewController *)vc; 27 | - (void)popViewController; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Package/ViewController/PrepareEnvironment/MYPackagePrepareEnvironmentViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackagePrepareEnvironmentViewController.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/23. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBaseViewController.h" 10 | 11 | @interface MYPackagePrepareEnvironmentViewController : MYPackageBaseViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Package/ViewController/PrepareEnvironment/MYPackagePrepareEnvironmentViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackagePrepareEnvironmentViewController.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/23. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackagePrepareEnvironmentViewController.h" 10 | #import "MYPackageContainerViewController.h" 11 | #import "MYPackageSelectProjectViewController.h" 12 | #import "MYPackageSelectSchemeViewController.h" 13 | 14 | #import "MYPackageInitTask.h" 15 | #import "MYPackagePrepareEnvironmentTask.h" 16 | 17 | @interface MYPackagePrepareEnvironmentViewController () 18 | 19 | @end 20 | 21 | @implementation MYPackagePrepareEnvironmentViewController 22 | 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | self.showBackButton = NO; 26 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 27 | if ([self runTasks:@[NSStringFromClass([MYPackageInitTask class]), 28 | NSStringFromClass([MYPackagePrepareEnvironmentTask class]) 29 | ] 30 | autoOrder:NO]) { 31 | dispatch_main_async_safe(^{ 32 | if (self.config.workspaceFilePath) { 33 | [self.containerVC changeTopViewController:[[MYPackageSelectSchemeViewController alloc] init]]; 34 | } else { 35 | [self.containerVC changeTopViewController:[[MYPackageSelectProjectViewController alloc] init]]; 36 | } 37 | }); 38 | } 39 | }); 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Package/ViewController/SelectProject/MYPackageSelectProjectTableViewHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageSelectProjectTableViewHeader.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/26. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MYPackageSelectProjectTableViewHeader : NSView 12 | 13 | @property (weak) IBOutlet NSTextField *titleLabel; 14 | @property (weak) IBOutlet NSButton *otherButton; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Package/ViewController/SelectProject/MYPackageSelectProjectTableViewHeader.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageSelectProjectTableViewHeader.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/26. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageSelectProjectTableViewHeader.h" 10 | 11 | @implementation MYPackageSelectProjectTableViewHeader 12 | 13 | - (void)drawRect:(NSRect)dirtyRect { 14 | [super drawRect:dirtyRect]; 15 | 16 | // Drawing code here. 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Package/ViewController/SelectProject/MYPackageSelectProjectTableViewHeader.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Package/ViewController/SelectProject/MYPackageSelectProjectTableViewRow.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageSelectProjectTableViewRow.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/26. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MYPackageSelectProjectTableViewRow : NSView 12 | @property (weak) IBOutlet NSTextField *nameLabel; 13 | @property (weak) IBOutlet NSTextField *pathLabel; 14 | @property (weak) IBOutlet NSButton *deleteButton; 15 | 16 | @property (nonatomic, strong) NSString *path; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Package/ViewController/SelectProject/MYPackageSelectProjectTableViewRow.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageSelectProjectTableViewRow.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/26. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageSelectProjectTableViewRow.h" 10 | 11 | @implementation MYPackageSelectProjectTableViewRow 12 | 13 | - (void)awakeFromNib { 14 | self.pathLabel.lineBreakMode = NSLineBreakByTruncatingHead; 15 | } 16 | 17 | - (void)setPath:(NSString *)path { 18 | _path = path; 19 | [self.nameLabel setStringValue:[path lastPathComponent]]; 20 | [self.pathLabel setStringValue:[path stringByDeletingLastPathComponent]]; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Package/ViewController/SelectProject/MYPackageSelectProjectViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageSelectProjectViewController.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/23. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBaseViewController.h" 10 | 11 | @interface MYPackageSelectProjectViewController : MYPackageBaseViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Package/ViewController/SelectScheme/MYPackageSelectSchemeTableViewRow.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageSelectSchemeTableViewRow.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/27. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MYPackageSelectSchemeTableViewRow : NSView 12 | 13 | @property (weak) IBOutlet NSTextField *titleLabel; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Package/ViewController/SelectScheme/MYPackageSelectSchemeTableViewRow.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageSelectSchemeTableViewRow.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/27. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageSelectSchemeTableViewRow.h" 10 | 11 | @implementation MYPackageSelectSchemeTableViewRow 12 | 13 | - (void)drawRect:(NSRect)dirtyRect { 14 | [super drawRect:dirtyRect]; 15 | 16 | // Drawing code here. 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Package/ViewController/SelectScheme/MYPackageSelectSchemeTableViewRow.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Package/ViewController/SelectScheme/MYPackageSelectSchemeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageSelectSchemeViewController.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/23. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBaseViewController.h" 10 | 11 | @interface MYPackageSelectSchemeViewController : MYPackageBaseViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Package/ViewController/SelectScheme/MYPackageSelectSchemeViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageSelectSchemeViewController.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/23. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageSelectSchemeViewController.h" 10 | #import "MYPackageContainerViewController.h" 11 | #import "MYPackageSelectTaskViewController.h" 12 | #import "MYPackageBuildAppViewController.h" 13 | 14 | #import "MYPackageSelectSchemeTableViewRow.h" 15 | #import "MYPackageTableRowView.h" 16 | 17 | #import "MYPackageListSchemeTask.h" 18 | #import "MYPackageAnalyzeSchemeTask.h" 19 | #import "MYPackageAnalyzeProjectTask.h" 20 | #import "MYPackageAnalyzeGitTask.h" 21 | #import "MYPackageAnalyzeTargetTask.h" 22 | 23 | @interface MYPackageSelectSchemeViewController () 24 | 25 | @property (weak) IBOutlet NSTableView *tableView; 26 | 27 | @end 28 | 29 | @implementation MYPackageSelectSchemeViewController 30 | 31 | - (void)viewDidLoad { 32 | [super viewDidLoad]; 33 | [self.tableView sizeLastColumnToFit]; 34 | [self.tableView registerNib:[[NSNib alloc] initWithNibNamed:NSStringFromClass([MYPackageSelectSchemeTableViewRow class]) bundle:nil] 35 | forIdentifier :@"row"]; 36 | } 37 | 38 | - (void)viewDidAppear { 39 | [super viewDidAppear]; 40 | self.title = self.config.workspace.name; 41 | [self updateTableViewData]; 42 | } 43 | 44 | - (void)updateTableViewData { 45 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 46 | if ([self runTasks:@[NSStringFromClass([MYPackageListSchemeTask class])] autoOrder:NO]) { 47 | dispatch_main_async_safe(^{ 48 | [self.tableView reloadData]; 49 | }); 50 | } 51 | }); 52 | } 53 | 54 | #pragma mark - table view 55 | - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { 56 | return [self.config.workspace.schemes count]; 57 | } 58 | 59 | - (nullable NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row { 60 | return [[MYPackageTableRowView alloc] init]; 61 | } 62 | 63 | - (nullable NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row { 64 | MYPackageSelectSchemeTableViewRow *rowView = (MYPackageSelectSchemeTableViewRow *)[tableView makeViewWithIdentifier:@"row" owner:self]; 65 | [rowView.titleLabel setStringValue:[self.config.workspace.schemes[row] name]]; 66 | return rowView; 67 | } 68 | 69 | - (IBAction)tableViewDoubleAction:(id)sender { 70 | if (self.config.workspace.schemes.count > self.tableView.selectedRow) { 71 | [self selectScheme:self.config.workspace.schemes[self.tableView.selectedRow]]; 72 | } 73 | } 74 | 75 | #pragma mark - task 76 | - (void)selectScheme:(MYPackageScheme *)scheme { 77 | self.config.selectedScheme = scheme; 78 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 79 | if ([self runTasks:@[NSStringFromClass([MYPackageAnalyzeSchemeTask class]), 80 | NSStringFromClass([MYPackageAnalyzeProjectTask class]), 81 | NSStringFromClass([MYPackageAnalyzeTargetTask class]) // 用于读取版本号 82 | ] 83 | autoOrder:YES]) { 84 | dispatch_main_async_safe(^{ 85 | MYPackageBaseViewController *vc; 86 | if (self.config.appTarget) { 87 | vc = [[MYPackageBuildAppViewController alloc] init]; 88 | } else { 89 | vc = [[MYPackageSelectTaskViewController alloc] init]; 90 | } 91 | [self.containerVC pushViewController:vc]; 92 | }); 93 | } 94 | }); 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /Package/ViewController/SelectTask/MYPackageSelectTaskViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MYPackageSelectTaskViewController.h 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/11/27. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import "MYPackageBaseViewController.h" 10 | 11 | @interface MYPackageSelectTaskViewController : MYPackageBaseViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Package/auto-update-build-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # auto-update-build-version.sh 4 | # 5 | # 自动修改 Build 号为当前时间 6 | # 7 | # 将 当前时间/Git提交时间 格式化为 年月日时分 的格式,保存到输出的 Info.plist 中,不修改项目的 Build 配置。 8 | # 9 | # 使用方法: 10 | # 1. 将该脚本加入项目的某个目录下,不需要添加到 Xcode。 11 | # 2. 在 Xcode 的对应项目 target -> Build Phases,添加一个 Shell 到最后: 12 | # "${SRCROOT}/脚本相对路径" 13 | # 14 | 15 | INFOPLISTPATH="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}" 16 | 17 | if [ $1 == "GIT" ]; then 18 | # Apple Git 不支持日期格式化!! 19 | # DATE=$(git show -s --format=%cd --date=format:"%y%m%d%H%M" HEAD) 20 | DATE=`date -jf "%Y-%m-%d %H:%M:%S %z" "$(git show -s --format=%ci HEAD)" +%y%m%d%H%M` 21 | else 22 | DATE=`date +%y%m%d%H%M` 23 | fi 24 | 25 | defaults write "${INFOPLISTPATH}" CFBundleVersion "$DATE" 26 | -------------------------------------------------------------------------------- /Package/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Package 4 | // 5 | // Created by Whirlwind on 15/10/21. 6 | // Copyright © 2015年 taobao. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MYPackageCommandLine.h" 11 | 12 | int main(int argc, const char *argv[]) { 13 | if ([MYPackageCommandLine canRunWithCommandLineMode]) { 14 | @autoreleasepool { 15 | __block int s = -100; 16 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 17 | s = [MYPackageCommandLine run]; 18 | dispatch_sync(dispatch_get_main_queue(), ^{ 19 | CFRunLoopStop(CFRunLoopGetCurrent()); 20 | }); 21 | }); 22 | CFRunLoopRun(); 23 | return s; 24 | } 25 | } else { 26 | return NSApplicationMain(argc, argv); 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | 2 | platform :osx, "10.10" 3 | 4 | target 'Package' do 5 | podspec 6 | end -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - CocoaAsyncSocket (7.6.3) 3 | - Criollo (0.5.4): 4 | - CocoaAsyncSocket (~> 7.6) 5 | 6 | DEPENDENCIES: 7 | - Criollo 8 | 9 | SPEC REPOS: 10 | https://github.com/cocoapods/specs.git: 11 | - CocoaAsyncSocket 12 | - Criollo 13 | 14 | SPEC CHECKSUMS: 15 | CocoaAsyncSocket: eafaa68a7e0ec99ead0a7b35015e0bf25d2c8987 16 | Criollo: 41d5ee161ba483a0a5adecc3bc1e19af29c872cc 17 | 18 | PODFILE CHECKSUM: a91c3efc3aed4d3af2cb3b501fa5fdf8aa918242 19 | 20 | COCOAPODS: 1.5.3 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XcodePackage 2 | - 3 | Package 是一个标准化的打包工具,初衷是遵循 Apple 开发习惯,全程使用标准流程,帮助开发人员直接生成 SDK 包,同时生成 Podspec 文件。 4 | 5 | ### 功能: 6 | 7 | 1. 使用 Xcode 配置输出产物,并生成含模拟器、真机多种架构的通用二进制; 8 | - 自动识别正在打开的 Xcode 项目,方便快速打包; 9 | - 自动分析用户 Git 信息以及项目信息,提取出必要信息,作为生成的 Podspec 参考信息; 10 | - 发布成功将自动对 Git 项目打 Tag; 11 | - 支持 GUI、命令行双模式,少量的交互模式,简化开发人员学习成本; 12 | - 支持 subspec 模式打包 13 | 14 | ### 支持类型: 15 | 1. 静态/动态 Framework 16 | - 静态二进制(`.a`) 17 | - 动态二进制(`.dylib`) 18 | - 资源包(`.bundle`) 19 | - Object File (`.o`) 20 | 21 | ### 使用方法 22 | 1. 使用 Xcode 标准模版创建项目,并设置好相应 Target 参数 23 | - 如果项目使用了 CocoaPods 作为依赖管理,最好准确编写源码依赖用的 podspec,会从该 podspec 中读取必要信息 24 | - 保证项目能正常运行 25 | - 打开 Package App,选择要打包的项目 26 | - 请确保打包项目的 scheme 勾选了 `Shared` 类型 27 | - 根据界面提示操作即可 28 | - 输出产物请到 `菜单` -> `Window` -> `打开输出目录` 查看 29 | - 查看日志请到 `菜单` -> `Window` -> `显示日志` 查看 30 | 31 | ### Podspec 生成规则 (IPA 无效) 32 | 33 | | podspec 值 | 数据来源 | 数据来源类型 | 34 | |:-----------:|:--------------------------------------------------------:|:---------:| 35 | | name | Project 名 | [Xcode] | 36 | | version | Target 的 `Deployment Target` | [Xcode] | 37 | | authors | Git config | [Git] | 38 | | homepage | Git config | [Git] | 39 | | description | 项目根目录的 podspec,默认同 `name` | [Podspec] | 40 | | source | 占位符 | - | 41 | | license | 项目根目录的 podspec,默认 `MIT` | [Podspec] | 42 | | source_code | Git config | [Git] | 43 | | frameworks | Demo 中 link frameowrks | [Xcode] | 44 | | libraries | Demo 中 link libraries | [Xcode] | 45 | | xcconfig | Demo 中 `HEADER_SEARCH_PATHS`、`OTHER_LDFLAGS`、`OTHER_CFLAGS` | [Xcode] | 46 | | default_subspec | 项目根目录的 podspec | [Podspec] | 47 | 48 | | | .a | .dylib | .framework (static) | .framework(dynamtic) | .bundle | .o | 49 | |:-------------------:|:---:|:-------:|:----------------------------------------------------------------------------------------------------:|:----------------------:|:---------:|:--:| 50 | | source_files | - | - | xx.framework/Headers/* | xx.framework/Headers/* | - | - | 51 | | resources | - | - | 如果存在 Resources 文件夹: xx.framework/Resources/* 否则 xx.framework/* | - | xx.bundle | - | 52 | | exclude_files | - | - | xx.framework 目录下 Headers, PrivateHeaders, Modules, Versions, CodeSignature, .DS_Store, Info.plist | - | - | - | 53 | | vendored_frameworks | - | - | xx.framework | xx.framework | - | - | 54 | | vendored_libraries | xx.a | xx.dylib | - | - | - | - | | - | - | 55 | 56 | ### Subspec 57 | 只支持一层 subspec,每个 target 为一个 subspec,当添加多个 target 到一个 scheme 中,自动判断为生成 subspec。 58 | 59 | subspec 中的 name 为 target 名,如果希望 subspec 名字和生成的 framework 名字不同,则可以修改对应 target 的 `PRODUCT_NAME` 和 target 名字不同。 60 | 61 | ### Todo 62 | 1. 支持 IPA 产物并扫码安装 63 | 64 | ### QA 65 | 1. 安装 Gem 环境失败,详细日志提示如下: 66 | 67 | ``` 68 | /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/include/ruby-2.0.0/ruby/ruby.h:24:10: fatal error: 'ruby/config.h' file not found 69 | #include "ruby/config.h" 70 | ^ 71 | 1 error generated. 72 | make: *** [generator.o] Error 1 73 | make failed, exit code 2 74 | ``` 75 | 解决方法:前往苹果下载中心下载并重装 Command Line Tool,然后重新启动 App 即可。 76 | 77 | - 选择了项目无法看到 Scheme,提示 `未找到 Shared Scheme`: 78 | 79 | 在 Xcode 中,选择 `Scheme` -> `Manage Schemes...` -> `勾选对应 Scheme 的 Shared 的勾` 80 | -------------------------------------------------------------------------------- /XcodePackage.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | 4 | s.name = "XcodePackage" 5 | s.version = "0.0.1" 6 | s.summary = "Xcode auto package tool." 7 | s.description = <<-DESC 8 | Package will build framework and create its podspec. 9 | DESC 10 | 11 | s.homepage = "http://github.com/dijkst/XcodePackage.git" 12 | s.license = "MIT" 13 | s.platform = :osx, "10.10" 14 | s.author = { "Whirlwind" => "whirlwindjames@foxmail.com" } 15 | s.source = { :git => "git@github.com:dijkst/XcodePackage.git", :tag => "#{s.version}" } 16 | 17 | s.source_files = "{Core,ObjCCommandLine,Package}/**/*.{h,m,mm}" 18 | s.exclude_files = "**/MainMenu.xib" 19 | s.resources = "Core/Task/Prepare/Script/{RubyEnv,GemEnv}", "Core/**/*.{sh,rb,xcconfig}", "Package/**/*/*.xib", "Package/*.xcassets" 20 | s.prefix_header_file = "Package/PrefixHeader.pch" 21 | s.dependency "Criollo" 22 | end 23 | --------------------------------------------------------------------------------