fix: pbxproj now parses with Xcode 26

Root cause: filenames containing '+' (Color+Extensions.swift,
DateFormatter+Extensions.swift, URL+Feishu.swift) had their path values
unquoted in PBXFileReference entries. In NeXTSTEP plist format, '+'
is an operator character — the parser interpreted 'path = Utils/Color'
followed by '+Extensions.swift' as an addition expression instead of
a file path, causing a parse error.

Fixes:
- Added quote_path() to escape paths with special characters
- Regenerated pbxproj with all 25 source files, subdirectory groups
- Fixed workspace contents.xcworkspacedata (was empty)
- Cleaned up generator script (removed dead code, added scheme gen)
- Proper SWIFT_OPTIMIZATION_LEVEL quoting

Verified: xcodebuild -list parses the project successfully.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
tigerenwork 2026-06-28 21:33:02 +08:00
parent c57ab859eb
commit bda106dcad
4 changed files with 549 additions and 527 deletions

View File

@ -1,73 +1,74 @@
// !$*UTF8*$! // !$*UTF8*$!
{ {
archiveVersion = 1; archiveVersion = 1;
classes = {}; classes = {
};
objectVersion = 56; objectVersion = 56;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
C70061E61A4A91DEE4DCD093 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2ADE595D33ADF5BA01B253B; }; F6750BB1FBEE5BC90DD6BDBC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207CA4381D0CFEC95263A2DE; };
264AF91CD3111E25300CB673 /* BuggerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7705247A59F50F6B6E6D286F; }; 15550246A9E8DAF25E690478 /* BuggerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDB7AD270E93EA343975AC6; };
93E674C67BBD94AE8F60D9C2 /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE8899127BDCD00F1D39E8FD; }; 58F16D20CC26D8248055CC2C /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBEF4F77A6A2E2D041EF581; };
934A2D27BD52EA7EA9138148 /* Bug.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4DE27079694EBA15A0DFAAB; }; 4EA9BDBA3F98AF4A41712A18 /* Bug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846CA001267896C54A78441A; };
40F0A29391DC7C7D5BD3AC86 /* BugChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE785F676266AFB5EA636597; }; DDB9EB345FEBB95DD9976364 /* BugChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B24E1E1D32F7B774D12DD85; };
8AF76AF5588E865614CBEB3F /* BugPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADA8A640EF741D9404316C39; }; A33561284CA44A0045902BCC /* BugPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5359156BD70B765D2BF4E718; };
6E97B8FF66F15C45F5BBCDC3 /* BugStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649CAAB9009813D6C1FD07E3; }; E4CBAE27491AF1C52944FA91 /* BugStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646C676356A4125B8E8D5FA5; };
7E7A04B78BD2D7DA8B923E89 /* AppStateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3148982E653318C7E3CD13F; }; D2855F82E68FADA8BEA43086 /* AppStateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DD178095EFCE7BC121C1C4; };
1EE9930CFF29314317C80B41 /* FeishuAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 264DA84D71E29D64DEC1B038; }; 4309E8C70AE99466545A3E37 /* FeishuAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A2C50E47966E8ADB0446A0C; };
EA309C97F6828294AE038EE0 /* FeishuError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A9F363FE066CAE6226A050; }; CB529549DB1DBB5F02884300 /* FeishuError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A83E76FBB83849E4B6704D3; };
A53C0F63FE7B64140EE84896 /* FeishuModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 973BF857A0044A0262A1F56F; }; CAE624453A3F0375D872170A /* FeishuModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE9FBC39502FA635EF1A6A5C; };
32E0CB6A605B7E033826F85C /* FeishuService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66321820D32BCC5D05265115; }; 29EE2FE56882F97E14AAF58F /* FeishuService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21AC33F380B7DC34C39B61A6; };
7B0310CEB785902145E910CE /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C98E94F5A859B0EEC7326C60; }; 6148104B311B60C79B3B8572 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59CC175DE8E9FE3250757D33; };
64973B14EC100A258F5D5DF1 /* PollerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2692AA96D65949109FEC0E; }; 56D44DCBAF4FE0E1D9E620FA /* PollerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F4628E7B599B69B499E09E9; };
DB560F1DC0B066BE60979A42 /* TokenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9677914DC46FD106A90CA724; }; B882D310EB2B5D032375CDDF /* TokenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3775F77DD36C27160A8C; };
570EEFE5A198913D1E28D539 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 968D99A1DA7131447D075F27; }; C5D9B1FACE813CD43236BB77 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F99094B0F609809EBDB5F4A; };
523A4BEC8BE348013BA23CCB /* DateFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BE6905A92E43B24E24CDE29; }; 29AE37B117A1422EA21570CA /* DateFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C935E6B4D1813AB01186A0; };
6FBC99D9861B64AF3C21FFC2 /* KeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 527F9B3FF513B72F175A8429; }; 64F1EE978536D1CB02B8F0D3 /* KeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF06E1030AA21DA8190169A8; };
690FBDF23267CDB564AA9014 /* URL+Feishu.swift in Sources */ = {isa = PBXBuildFile; fileRef = E390AA6A998B6C83BD779018; }; 1FB7E762C0B6568C47B48EF0 /* URL+Feishu.swift in Sources */ = {isa = PBXBuildFile; fileRef = F888AA5F655343B19839B4C2; };
50C462B4A25F77CD93FFBB49 /* BugStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C251D0198118D6B5D4B7C69; }; AECED74595B57A400437B9FD /* BugStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F52FB34B285D91A3E4670; };
BD6E0970804F37FAA7DBB468 /* FloatingWidgetWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DC24ADCEDA5C16DFF9FA6EA; }; 485A69E15EF1D805F50DF364 /* FloatingWidgetWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2FE32B404AB4388A066991; };
5AEABE301EC2352533870063 /* BugListPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D10F852D16E2F4FC269FD2; }; 1E3C1F40E2FF64A041856909 /* BugListPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEABFC668F7B082847F65D38; };
E4911DC6264A1006B7FDCF30 /* BugRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F3DB4764E379E663475EBD; }; 3467A50268399ABE9804D4D4 /* BugRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1129D6F630904F03D06B5DC4; };
EA6166A18AD94D499E170D07 /* OAuthSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29172EAB6F6B4526BDA23C2; }; B066D78DA6F57A882A61C0B2 /* OAuthSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C0A744D4F5DB88B1496176B; };
6B62FD564DD3D1A983DFB3EB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9AD7665552E1BC2E76337C; }; 7B0EAA6D83D40A593E36EE31 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D3693AA93F823859D15256D; };
2D2411A895B80B698B8E2A82 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A93EEBDA5A740C84995D959; }; C3F7FEB179B079BDDD2AA212 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 09B9A4D84F3F604C49171A89; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
EF0182DF85A7CD0319333F24 /* Bugger.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bugger.app; sourceTree = BUILT_PRODUCTS_DIR; }; EF0182DF85A7CD0319333F24 /* Bugger.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Bugger.app; sourceTree = BUILT_PRODUCTS_DIR; };
B2ADE595D33ADF5BA01B253B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 207CA4381D0CFEC95263A2DE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7705247A59F50F6B6E6D286F /* BuggerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuggerApp.swift; sourceTree = "<group>"; }; DDDB7AD270E93EA343975AC6 /* BuggerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuggerApp.swift; sourceTree = "<group>"; };
AE8899127BDCD00F1D39E8FD /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfig.swift; sourceTree = "<group>"; }; 9FBEF4F77A6A2E2D041EF581 /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfig.swift; sourceTree = "<group>"; };
C4DE27079694EBA15A0DFAAB /* Bug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bug.swift; sourceTree = "<group>"; }; 846CA001267896C54A78441A /* Bug.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bug.swift; sourceTree = "<group>"; };
FE785F676266AFB5EA636597 /* BugChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugChange.swift; sourceTree = "<group>"; }; 0B24E1E1D32F7B774D12DD85 /* BugChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugChange.swift; sourceTree = "<group>"; };
ADA8A640EF741D9404316C39 /* BugPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugPriority.swift; sourceTree = "<group>"; }; 5359156BD70B765D2BF4E718 /* BugPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugPriority.swift; sourceTree = "<group>"; };
649CAAB9009813D6C1FD07E3 /* BugStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStatus.swift; sourceTree = "<group>"; }; 646C676356A4125B8E8D5FA5 /* BugStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStatus.swift; sourceTree = "<group>"; };
E3148982E653318C7E3CD13F /* AppStateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateService.swift; sourceTree = "<group>"; }; F1DD178095EFCE7BC121C1C4 /* AppStateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateService.swift; sourceTree = "<group>"; };
264DA84D71E29D64DEC1B038 /* FeishuAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuAuthService.swift; sourceTree = "<group>"; }; 6A2C50E47966E8ADB0446A0C /* FeishuAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuAuthService.swift; sourceTree = "<group>"; };
38A9F363FE066CAE6226A050 /* FeishuError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuError.swift; sourceTree = "<group>"; }; 2A83E76FBB83849E4B6704D3 /* FeishuError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuError.swift; sourceTree = "<group>"; };
973BF857A0044A0262A1F56F /* FeishuModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuModels.swift; sourceTree = "<group>"; }; FE9FBC39502FA635EF1A6A5C /* FeishuModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuModels.swift; sourceTree = "<group>"; };
66321820D32BCC5D05265115 /* FeishuService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuService.swift; sourceTree = "<group>"; }; 21AC33F380B7DC34C39B61A6 /* FeishuService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeishuService.swift; sourceTree = "<group>"; };
C98E94F5A859B0EEC7326C60 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; }; 59CC175DE8E9FE3250757D33 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
BD2692AA96D65949109FEC0E /* PollerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollerService.swift; sourceTree = "<group>"; }; 4F4628E7B599B69B499E09E9 /* PollerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollerService.swift; sourceTree = "<group>"; };
9677914DC46FD106A90CA724 /* TokenManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenManager.swift; sourceTree = "<group>"; }; 0E7E3775F77DD36C27160A8C /* TokenManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenManager.swift; sourceTree = "<group>"; };
968D99A1DA7131447D075F27 /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color+Extensions.swift; sourceTree = "<group>"; }; 0F99094B0F609809EBDB5F4A /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = "<group>"; };
0BE6905A92E43B24E24CDE29 /* DateFormatter+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormatter+Extensions.swift; sourceTree = "<group>"; }; 87C935E6B4D1813AB01186A0 /* DateFormatter+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extensions.swift"; sourceTree = "<group>"; };
527F9B3FF513B72F175A8429 /* KeychainHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainHelper.swift; sourceTree = "<group>"; }; EF06E1030AA21DA8190169A8 /* KeychainHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainHelper.swift; sourceTree = "<group>"; };
E390AA6A998B6C83BD779018 /* URL+Feishu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL+Feishu.swift; sourceTree = "<group>"; }; F888AA5F655343B19839B4C2 /* URL+Feishu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Feishu.swift"; sourceTree = "<group>"; };
6C251D0198118D6B5D4B7C69 /* BugStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStore.swift; sourceTree = "<group>"; }; 344F52FB34B285D91A3E4670 /* BugStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugStore.swift; sourceTree = "<group>"; };
5DC24ADCEDA5C16DFF9FA6EA /* FloatingWidgetWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingWidgetWindow.swift; sourceTree = "<group>"; }; 6E2FE32B404AB4388A066991 /* FloatingWidgetWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingWidgetWindow.swift; sourceTree = "<group>"; };
17D10F852D16E2F4FC269FD2 /* BugListPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugListPopover.swift; sourceTree = "<group>"; }; CEABFC668F7B082847F65D38 /* BugListPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugListPopover.swift; sourceTree = "<group>"; };
C3F3DB4764E379E663475EBD /* BugRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugRow.swift; sourceTree = "<group>"; }; 1129D6F630904F03D06B5DC4 /* BugRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugRow.swift; sourceTree = "<group>"; };
E29172EAB6F6B4526BDA23C2 /* OAuthSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthSetupView.swift; sourceTree = "<group>"; }; 3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthSetupView.swift; sourceTree = "<group>"; };
9F9AD7665552E1BC2E76337C /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; }; 2D3693AA93F823859D15256D /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
6A93EEBDA5A740C84995D959 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 09B9A4D84F3F604C49171A89 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
2EB7B1BD755DF7F2FB5F8392 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 73AB234B54673C838E33AC7F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
86B3BC083DF59791BE56CFAE /* Bugger.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Bugger.entitlements; sourceTree = "<group>"; }; E9B6FE6E06295095649B24A7 /* Bugger.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Bugger.entitlements; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
DE1439ACCFCEB73E6F9981A5 /* Frameworks */ = { 03303046F4622F1382FE219D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
@ -77,133 +78,133 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
8C4FDD7B635240BD562EBB49 = { 14ED1DECD6D906F498B9ECB5 /* Root */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
EE6F40F7464432A6C2A761DA /* Sources */, 287BE755981C092238309A9A /* Sources */,
7C4EE8955498EEF65ED6C84A /* Resources */, 8AD6A16612A2DB7B2F899787 /* Resources */,
B23D89B2D2DF3745A003AEB1 /* Products */, B23D89B2D2DF3745A003AEB1 /* Products */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
EE6F40F7464432A6C2A761DA /* Sources */ = { 287BE755981C092238309A9A /* Sources */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B2ADE595D33ADF5BA01B253B /* AppDelegate.swift */, 207CA4381D0CFEC95263A2DE /* AppDelegate.swift */,
7705247A59F50F6B6E6D286F /* BuggerApp.swift */, DDDB7AD270E93EA343975AC6 /* BuggerApp.swift */,
B805360275E47AAD7192E4AE /* Models */, 65997CD9BD6FFEF8851BF59E /* Models */,
EF310F3F9A8796C845F345F5 /* Services */, A8C46168E4DFCE29B056A4E2 /* Services */,
B38F908829C30D6B6F967EEA /* Utils */, A6646A2065F53ED50AD2CCA7 /* Utils */,
56714C7161F5C8195EA19816 /* ViewModels */, 6AB9730AA11690EFC8F21473 /* ViewModels */,
F4ED3EC6534567974F720F99 /* Views */, 31DF436612EC1C3CD1194AA1 /* Views */,
); );
path = Sources; path = Sources;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
B805360275E47AAD7192E4AE /* Models */ = { 65997CD9BD6FFEF8851BF59E /* Models */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
AE8899127BDCD00F1D39E8FD /* AppConfig.swift */, 9FBEF4F77A6A2E2D041EF581 /* AppConfig.swift */,
C4DE27079694EBA15A0DFAAB /* Bug.swift */, 846CA001267896C54A78441A /* Bug.swift */,
FE785F676266AFB5EA636597 /* BugChange.swift */, 0B24E1E1D32F7B774D12DD85 /* BugChange.swift */,
ADA8A640EF741D9404316C39 /* BugPriority.swift */, 5359156BD70B765D2BF4E718 /* BugPriority.swift */,
649CAAB9009813D6C1FD07E3 /* BugStatus.swift */, 646C676356A4125B8E8D5FA5 /* BugStatus.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
EF310F3F9A8796C845F345F5 /* Services */ = { A8C46168E4DFCE29B056A4E2 /* Services */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E3148982E653318C7E3CD13F /* AppStateService.swift */, F1DD178095EFCE7BC121C1C4 /* AppStateService.swift */,
0B0ED8A1BE17CC4C0B3A787B /* Feishu */, 59CC175DE8E9FE3250757D33 /* NotificationService.swift */,
C98E94F5A859B0EEC7326C60 /* NotificationService.swift */, 4F4628E7B599B69B499E09E9 /* PollerService.swift */,
BD2692AA96D65949109FEC0E /* PollerService.swift */, 0E7E3775F77DD36C27160A8C /* TokenManager.swift */,
9677914DC46FD106A90CA724 /* TokenManager.swift */, E0E23D3899D89889546FB428 /* Feishu */,
); );
path = Services; path = Services;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
0B0ED8A1BE17CC4C0B3A787B /* Feishu */ = { A6646A2065F53ED50AD2CCA7 /* Utils */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
264DA84D71E29D64DEC1B038 /* FeishuAuthService.swift */, 0F99094B0F609809EBDB5F4A /* Color+Extensions.swift */,
38A9F363FE066CAE6226A050 /* FeishuError.swift */, 87C935E6B4D1813AB01186A0 /* DateFormatter+Extensions.swift */,
973BF857A0044A0262A1F56F /* FeishuModels.swift */, EF06E1030AA21DA8190169A8 /* KeychainHelper.swift */,
66321820D32BCC5D05265115 /* FeishuService.swift */, F888AA5F655343B19839B4C2 /* URL+Feishu.swift */,
);
path = Feishu;
sourceTree = "<group>";
};
B38F908829C30D6B6F967EEA /* Utils */ = {
isa = PBXGroup;
children = (
968D99A1DA7131447D075F27 /* Color+Extensions.swift */,
0BE6905A92E43B24E24CDE29 /* DateFormatter+Extensions.swift */,
527F9B3FF513B72F175A8429 /* KeychainHelper.swift */,
E390AA6A998B6C83BD779018 /* URL+Feishu.swift */,
); );
path = Utils; path = Utils;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
56714C7161F5C8195EA19816 /* ViewModels */ = { 6AB9730AA11690EFC8F21473 /* ViewModels */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
6C251D0198118D6B5D4B7C69 /* BugStore.swift */, 344F52FB34B285D91A3E4670 /* BugStore.swift */,
); );
path = ViewModels; path = ViewModels;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
F4ED3EC6534567974F720F99 /* Views */ = { 31DF436612EC1C3CD1194AA1 /* Views */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
82B5ED67A4830EDA75C6CDE1 /* FloatingWidget */, E7E4AD241EEE594C1EEB9724 /* FloatingWidget */,
24C578F83DCF9CEAFCAF2E73 /* MenuBar */, EA9ED591BB8A18FEBD1B58A3 /* MenuBar */,
0308B80FA0CF6FBE7D8C7EB3 /* OAuth */, 74AD95EF49A7D01C83865FAC /* OAuth */,
3053DFBFF1BD7F53471B36BB /* Settings */, B70D2E54AD6B19DD455A8F82 /* Settings */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
82B5ED67A4830EDA75C6CDE1 /* FloatingWidget */ = { E0E23D3899D89889546FB428 /* Feishu */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
5DC24ADCEDA5C16DFF9FA6EA /* FloatingWidgetWindow.swift */, 6A2C50E47966E8ADB0446A0C /* FeishuAuthService.swift */,
2A83E76FBB83849E4B6704D3 /* FeishuError.swift */,
FE9FBC39502FA635EF1A6A5C /* FeishuModels.swift */,
21AC33F380B7DC34C39B61A6 /* FeishuService.swift */,
);
path = Feishu;
sourceTree = "<group>";
};
E7E4AD241EEE594C1EEB9724 /* FloatingWidget */ = {
isa = PBXGroup;
children = (
6E2FE32B404AB4388A066991 /* FloatingWidgetWindow.swift */,
); );
path = FloatingWidget; path = FloatingWidget;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
24C578F83DCF9CEAFCAF2E73 /* MenuBar */ = { EA9ED591BB8A18FEBD1B58A3 /* MenuBar */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
17D10F852D16E2F4FC269FD2 /* BugListPopover.swift */, CEABFC668F7B082847F65D38 /* BugListPopover.swift */,
C3F3DB4764E379E663475EBD /* BugRow.swift */, 1129D6F630904F03D06B5DC4 /* BugRow.swift */,
); );
path = MenuBar; path = MenuBar;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
0308B80FA0CF6FBE7D8C7EB3 /* OAuth */ = { 74AD95EF49A7D01C83865FAC /* OAuth */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E29172EAB6F6B4526BDA23C2 /* OAuthSetupView.swift */, 3C0A744D4F5DB88B1496176B /* OAuthSetupView.swift */,
); );
path = OAuth; path = OAuth;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
3053DFBFF1BD7F53471B36BB /* Settings */ = { B70D2E54AD6B19DD455A8F82 /* Settings */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
9F9AD7665552E1BC2E76337C /* SettingsView.swift */, 2D3693AA93F823859D15256D /* SettingsView.swift */,
); );
path = Settings; path = Settings;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
7C4EE8955498EEF65ED6C84A /* Resources */ = { 8AD6A16612A2DB7B2F899787 /* Resources */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
6A93EEBDA5A740C84995D959 /* Assets.xcassets */, 09B9A4D84F3F604C49171A89 /* Assets.xcassets */,
2EB7B1BD755DF7F2FB5F8392 /* Info.plist */, 73AB234B54673C838E33AC7F /* Info.plist */,
86B3BC083DF59791BE56CFAE /* Bugger.entitlements */, E9B6FE6E06295095649B24A7 /* Bugger.entitlements */,
); );
path = Resources; path = Resources;
sourceTree = "<group>"; sourceTree = "<group>";
@ -221,11 +222,11 @@
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
42AEFBAE01D2DFD981F7DA7D /* Bugger */ = { 42AEFBAE01D2DFD981F7DA7D /* Bugger */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 74C875FD1C5C02E06FAC0545 /* Build configuration list for PBXNativeTarget "Bugger" */; buildConfigurationList = F1F749C5DF06081CD2D04369 /* Build configuration list for PBXNativeTarget "Bugger" */;
buildPhases = ( buildPhases = (
3B7DFB0747829707F437ED7F /* Sources */, B1CCC9C70ADA89DF47C9677E /* Sources */,
DE1439ACCFCEB73E6F9981A5 /* Frameworks */, 03303046F4622F1382FE219D /* Frameworks */,
3519654520012F56AC9A0E86 /* Resources */, F05EFBB4EA8CE481794F9936 /* Resources */,
); );
buildRules = ( buildRules = (
); );
@ -243,15 +244,15 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
BuildIndependentTargetsInParallel = 1; BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1500; LastSwiftUpdateCheck = 2600;
LastUpgradeCheck = 1500; LastUpgradeCheck = 2600;
TargetAttributes = { TargetAttributes = {
42AEFBAE01D2DFD981F7DA7D = { 42AEFBAE01D2DFD981F7DA7D = {
CreatedOnToolsVersion = 15.0; CreatedOnToolsVersion = 26.0;
}; };
}; };
}; };
buildConfigurationList = DD445E777372328CDC08F332 /* Build configuration list for PBXProject "Bugger" */; buildConfigurationList = F4FFB6AD1F4DDC161F81C0DE /* Build configuration list for PBXProject "Bugger" */;
compatibilityVersion = "Xcode 14.0"; compatibilityVersion = "Xcode 14.0";
developmentRegion = en; developmentRegion = en;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
@ -259,7 +260,7 @@
en, en,
Base, Base,
); );
mainGroup = 8C4FDD7B635240BD562EBB49; mainGroup = 14ED1DECD6D906F498B9ECB5;
productRefGroup = B23D89B2D2DF3745A003AEB1 /* Products */; productRefGroup = B23D89B2D2DF3745A003AEB1 /* Products */;
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
@ -270,81 +271,76 @@
/* End PBXProject section */ /* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */ /* Begin PBXResourcesBuildPhase section */
3519654520012F56AC9A0E86 /* Resources */ = { F05EFBB4EA8CE481794F9936 /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
2D2411A895B80B698B8E2A82 /* Assets.xcassets in Resources */, C3F7FEB179B079BDDD2AA212 /* Assets.xcassets in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
3B7DFB0747829707F437ED7F /* Sources */ = { B1CCC9C70ADA89DF47C9677E /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
C70061E61A4A91DEE4DCD093 /* AppDelegate.swift in Sources */, F6750BB1FBEE5BC90DD6BDBC /* AppDelegate.swift in Sources */,
264AF91CD3111E25300CB673 /* BuggerApp.swift in Sources */, 15550246A9E8DAF25E690478 /* BuggerApp.swift in Sources */,
93E674C67BBD94AE8F60D9C2 /* AppConfig.swift in Sources */, 58F16D20CC26D8248055CC2C /* AppConfig.swift in Sources */,
934A2D27BD52EA7EA9138148 /* Bug.swift in Sources */, 4EA9BDBA3F98AF4A41712A18 /* Bug.swift in Sources */,
40F0A29391DC7C7D5BD3AC86 /* BugChange.swift in Sources */, DDB9EB345FEBB95DD9976364 /* BugChange.swift in Sources */,
8AF76AF5588E865614CBEB3F /* BugPriority.swift in Sources */, A33561284CA44A0045902BCC /* BugPriority.swift in Sources */,
6E97B8FF66F15C45F5BBCDC3 /* BugStatus.swift in Sources */, E4CBAE27491AF1C52944FA91 /* BugStatus.swift in Sources */,
7E7A04B78BD2D7DA8B923E89 /* AppStateService.swift in Sources */, D2855F82E68FADA8BEA43086 /* AppStateService.swift in Sources */,
1EE9930CFF29314317C80B41 /* FeishuAuthService.swift in Sources */, 4309E8C70AE99466545A3E37 /* FeishuAuthService.swift in Sources */,
EA309C97F6828294AE038EE0 /* FeishuError.swift in Sources */, CB529549DB1DBB5F02884300 /* FeishuError.swift in Sources */,
A53C0F63FE7B64140EE84896 /* FeishuModels.swift in Sources */, CAE624453A3F0375D872170A /* FeishuModels.swift in Sources */,
32E0CB6A605B7E033826F85C /* FeishuService.swift in Sources */, 29EE2FE56882F97E14AAF58F /* FeishuService.swift in Sources */,
7B0310CEB785902145E910CE /* NotificationService.swift in Sources */, 6148104B311B60C79B3B8572 /* NotificationService.swift in Sources */,
64973B14EC100A258F5D5DF1 /* PollerService.swift in Sources */, 56D44DCBAF4FE0E1D9E620FA /* PollerService.swift in Sources */,
DB560F1DC0B066BE60979A42 /* TokenManager.swift in Sources */, B882D310EB2B5D032375CDDF /* TokenManager.swift in Sources */,
570EEFE5A198913D1E28D539 /* Color+Extensions.swift in Sources */, C5D9B1FACE813CD43236BB77 /* Color+Extensions.swift in Sources */,
523A4BEC8BE348013BA23CCB /* DateFormatter+Extensions.swift in Sources */, 29AE37B117A1422EA21570CA /* DateFormatter+Extensions.swift in Sources */,
6FBC99D9861B64AF3C21FFC2 /* KeychainHelper.swift in Sources */, 64F1EE978536D1CB02B8F0D3 /* KeychainHelper.swift in Sources */,
690FBDF23267CDB564AA9014 /* URL+Feishu.swift in Sources */, 1FB7E762C0B6568C47B48EF0 /* URL+Feishu.swift in Sources */,
50C462B4A25F77CD93FFBB49 /* BugStore.swift in Sources */, AECED74595B57A400437B9FD /* BugStore.swift in Sources */,
BD6E0970804F37FAA7DBB468 /* FloatingWidgetWindow.swift in Sources */, 485A69E15EF1D805F50DF364 /* FloatingWidgetWindow.swift in Sources */,
5AEABE301EC2352533870063 /* BugListPopover.swift in Sources */, 1E3C1F40E2FF64A041856909 /* BugListPopover.swift in Sources */,
E4911DC6264A1006B7FDCF30 /* BugRow.swift in Sources */, 3467A50268399ABE9804D4D4 /* BugRow.swift in Sources */,
EA6166A18AD94D499E170D07 /* OAuthSetupView.swift in Sources */, B066D78DA6F57A882A61C0B2 /* OAuthSetupView.swift in Sources */,
6B62FD564DD3D1A983DFB3EB /* SettingsView.swift in Sources */, 7B0EAA6D83D40A593E36EE31 /* SettingsView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
F76147CE86058CC1939088C9 /* Debug */ = { 172CAC4F236E6C94A1763B32 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = -Onone; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
}; };
name = Debug; name = Debug;
}; };
97C89C522DCD700C1F32C445 /* Release */ = { 8FD30172609F662677CE533F /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEBUG_INFORMATION_FORMAT = dwarf-with-dsym;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
}; };
name = Release; name = Release;
}; };
36F7B61E0BB35F5EE850C3BE /* Debug */ = { BCB1273FA062F6C8B2456F08 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
@ -369,7 +365,7 @@
}; };
name = Debug; name = Debug;
}; };
C961A2B6F28F76EF702C6931 /* Release */ = { DE8F0F92492FDD4AE81004CA /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
@ -397,25 +393,26 @@
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
DD445E777372328CDC08F332 /* Build configuration list for PBXProject "Bugger" */ = { F1F749C5DF06081CD2D04369 /* Build configuration list for PBXNativeTarget "Bugger" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
F76147CE86058CC1939088C9 /* Debug */, BCB1273FA062F6C8B2456F08 /* Debug */,
97C89C522DCD700C1F32C445 /* Release */, DE8F0F92492FDD4AE81004CA /* Release */,
); );
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
74C875FD1C5C02E06FAC0545 /* Build configuration list for PBXNativeTarget "Bugger" */ = { F4FFB6AD1F4DDC161F81C0DE /* Build configuration list for PBXProject "Bugger" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
36F7B61E0BB35F5EE850C3BE /* Debug */, 172CAC4F236E6C94A1763B32 /* Debug */,
C961A2B6F28F76EF702C6931 /* Release */, 8FD30172609F662677CE533F /* Release */,
); );
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
}; };
rootObject = 46F86FAA6BBF9AC94A7E4595 /* Project object */; rootObject = 46F86FAA6BBF9AC94A7E4595 /* Project object */;
} }

View File

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Workspace <Workspace
version = "1.0"> version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace> </Workspace>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1500" LastUpgradeVersion = "2600"
version = "1.7"> version = "1.7">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Generate Bugger.xcodeproj from source tree.""" """Generate a valid Bugger.xcodeproj from the source tree."""
import hashlib import hashlib
import os import os
@ -8,419 +8,412 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent ROOT = Path(__file__).resolve().parent.parent
PROJECT_NAME = "Bugger" PROJECT_NAME = "Bugger"
BUNDLE_ID = "com.xorbitlab.bugger" BUNDLE_ID = "com.xorbitlab.bugger"
SOURCE_ROOT = ROOT / "Sources" SOURCE_ROOT = ROOT / "Sources"
RESOURCE_ROOT = ROOT / "Resources" RESOURCE_ROOT = ROOT / "Resources"
PROJECT_DIR = ROOT / f"{PROJECT_NAME}.xcodeproj"
def uuid(seed: str) -> str: def uuid(seed: str) -> str:
digest = hashlib.md5(seed.encode()).hexdigest().upper() return hashlib.md5(seed.encode()).hexdigest().upper()[:24]
return digest[:24]
def collect_swift_files() -> list[Path]: def quote_path(name: str) -> str:
return sorted(SOURCE_ROOT.rglob("*.swift")) """Quote a path value if it contains characters unsafe in NeXTSTEP plist format.
In NeXTSTEP plist, unquoted strings can only contain [a-zA-Z0-9_./$-].
Characters like +, -, *, etc. act as operators and must be quoted.
"""
safe_chars = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_./$-")
if all(c in safe_chars for c in name):
return name
return f'"{name}"'
def collect_resources() -> list[Path]: def collect_swift_files() -> dict[str, str]:
resources = [] """Return {relative_path_in_sources: absolute_path} for all .swift files."""
for path in sorted(RESOURCE_ROOT.rglob("*")): files = {}
if path.is_file(): for path in sorted(SOURCE_ROOT.rglob("*.swift")):
resources.append(path) rel = str(path.relative_to(SOURCE_ROOT))
return resources files[rel] = str(path)
return files
def pbxproj() -> str: def build_pbxproj() -> str:
swift_files = collect_swift_files() swift_files = collect_swift_files()
resources = collect_resources() sorted_files = sorted(swift_files.keys())
project_id = uuid("project") # ---- UUIDs ----
# File references
f_uuids = {f: uuid(f"fr-{f}") for f in sorted_files}
b_uuids = {f: uuid(f"bf-{f}") for f in sorted_files}
# Resources
asset_ref = uuid("res-assets")
info_ref = uuid("res-info-plist")
ent_ref = uuid("res-entitlements")
asset_build = uuid("res-build-assets")
# Product
product_ref = uuid("product-ref")
# Build phases
frameworks_phase = uuid("fw-phase")
resources_phase = uuid("res-phase")
sources_phase = uuid("src-phase")
# Target
target_id = uuid("target") target_id = uuid("target")
sources_phase_id = uuid("sources-phase") target_cfg_list = uuid("targ-cfg-list")
resources_phase_id = uuid("resources-phase") target_dbg = uuid("targ-dbg")
frameworks_phase_id = uuid("frameworks-phase") target_rel = uuid("targ-rel")
product_ref_id = uuid("product-ref")
project_config_list_id = uuid("project-config-list")
target_config_list_id = uuid("target-config-list")
debug_config_id = uuid("debug-config")
release_config_id = uuid("release-config")
target_debug_config_id = uuid("target-debug-config")
target_release_config_id = uuid("target-release-config")
main_group_id = uuid("main-group") # Project
products_group_id = uuid("products-group") project_id = uuid("project")
sources_group_id = uuid("sources-group") project_cfg_list = uuid("proj-cfg-list")
resources_group_id = uuid("resources-group") project_dbg = uuid("proj-dbg")
project_rel = uuid("proj-rel")
file_refs: dict[Path, str] = {} # Groups
build_files: list[tuple[str, str, str]] = [] root_group = uuid("root-group")
sources_group = uuid("sources-group")
products_group = uuid("products-group")
resources_group = uuid("resources-group")
for path in swift_files: # Build group hierarchy from directory structure
ref_id = uuid(f"file-{path}") all_groups = set()
build_id = uuid(f"build-{path}") for f in sorted_files:
file_refs[path] = ref_id parts = f.split("/")[:-1]
build_files.append((build_id, ref_id, "Sources")) for i in range(len(parts)):
all_groups.add("/".join(parts[:i + 1]))
sorted_groups = sorted(all_groups, key=lambda g: (g.count("/"), g))
group_uuids = {g: uuid(f"grp-{g}") for g in sorted_groups}
resource_refs: dict[Path, str] = {} # Determine which files/children go where
for path in resources: top_files = [f for f in sorted_files if "/" not in f]
ref_id = uuid(f"res-{path}") top_dirs = {g for g in sorted_groups if "/" not in g}
build_id = uuid(f"res-build-{path}")
resource_refs[path] = ref_id
build_files.append((build_id, ref_id, "Resources"))
def group_for(path: Path, parent: str) -> str: def dir_files(group: str) -> list[str]:
rel = path.relative_to(ROOT) """Files directly inside this group directory."""
parts = rel.parts[:-1] prefix = group + "/"
group_ids = [main_group_id] return sorted(f for f in sorted_files
current = "" if f.startswith(prefix) and "/" not in f[len(prefix):])
for part in parts:
current = f"{current}/{part}"
group_ids.append(uuid(f"group-{current}"))
return group_ids[-1]
groups: dict[str, list[str]] = {} def subdirs(group: str) -> list[str]:
child_groups: dict[str, set[str]] = {} """Subdirectory groups directly under this group."""
prefix = group + "/"
return sorted(sg for sg in sorted_groups
if sg.startswith(prefix) and "/" not in sg[len(prefix):])
def add_to_group(group_id: str, child_id: str): # ---- Build output ----
groups.setdefault(group_id, []).append(child_id) b: list[str] = []
add_to_group(main_group_id, sources_group_id) def L(line: str = "", indent: int = 0):
add_to_group(main_group_id, resources_group_id) b.append("\t" * indent + line)
add_to_group(main_group_id, products_group_id)
add_to_group(products_group_id, product_ref_id)
def ensure_group_chain(rel_parts: tuple[str, ...], root_group: str): L("// !$*UTF8*$!")
current = root_group L("{")
built = [] L("archiveVersion = 1;", 1)
for part in rel_parts: L("classes = {", 1)
built.append(part) L("};", 1)
gid = uuid(f"group-{'/'.join(built)}") L("objectVersion = 56;", 1)
if gid not in groups and gid != current: L("objects = {", 1)
add_to_group(current, gid) L()
current = gid
return current
for path in swift_files: # -- PBXBuildFile --
rel = path.relative_to(SOURCE_ROOT) L("/* Begin PBXBuildFile section */")
parent = ensure_group_chain(rel.parts[:-1], sources_group_id) for f in sorted_files:
add_to_group(parent, file_refs[path]) name = f.split("/")[-1]
L(f'{b_uuids[f]} /* {name} in Sources */ = {{isa = PBXBuildFile; fileRef = {f_uuids[f]}; }};', 2)
L(f'{asset_build} /* Assets.xcassets in Resources */ = {{isa = PBXBuildFile; fileRef = {asset_ref}; }};', 2)
L("/* End PBXBuildFile section */")
L()
for path in resources: # -- PBXFileReference --
rel = path.relative_to(RESOURCE_ROOT) L("/* Begin PBXFileReference section */")
if len(rel.parts) > 1: L(f'{product_ref} /* {PROJECT_NAME}.app */ = {{isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = {PROJECT_NAME}.app; sourceTree = BUILT_PRODUCTS_DIR; }};', 2)
parent = ensure_group_chain(rel.parts[:-1], resources_group_id) for f in sorted_files:
else: name = f.split("/")[-1]
parent = resources_group_id L(f'{f_uuids[f]} /* {name} */ = {{isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = {quote_path(name)}; sourceTree = "<group>"; }};', 2)
add_to_group(parent, resource_refs[path]) L(f'{asset_ref} /* Assets.xcassets */ = {{isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }};', 2)
L(f'{info_ref} /* Info.plist */ = {{isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }};', 2)
L(f'{ent_ref} /* Bugger.entitlements */ = {{isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Bugger.entitlements; sourceTree = "<group>"; }};', 2)
L("/* End PBXFileReference section */")
L()
lines: list[str] = [] # -- PBXFrameworksBuildPhase --
lines.append("// !$*UTF8*$!") L("/* Begin PBXFrameworksBuildPhase section */")
lines.append("{") L(f'{frameworks_phase} /* Frameworks */ = {{', 2)
lines.append("\tarchiveVersion = 1;") L("isa = PBXFrameworksBuildPhase;", 3)
lines.append("\tclasses = {};") L("buildActionMask = 2147483647;", 3)
lines.append("\tobjectVersion = 56;") L("files = (", 3)
lines.append("\tobjects = {") L(");", 3)
L("runOnlyForDeploymentPostprocessing = 0;", 3)
L("};", 2)
L("/* End PBXFrameworksBuildPhase section */")
L()
lines.append(f"\n/* Begin PBXBuildFile section */") # -- PBXGroup --
for build_id, ref_id, kind in build_files: L("/* Begin PBXGroup section */")
if kind == "Sources":
lines.append(f"\t\t{build_id} /* {Path(ref_id).name} in Sources */ = {{isa = PBXBuildFile; fileRef = {ref_id}; }};")
else:
lines.append(f"\t\t{build_id} /* {ref_id} in Resources */ = {{isa = PBXBuildFile; fileRef = {ref_id}; }};")
lines.append("/* End PBXBuildFile section */\n")
lines.append("/* Begin PBXFileReference section */") # Root group
lines.append(f"\t\t{product_ref_id} /* {PROJECT_NAME}.app */ = {{isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = {PROJECT_NAME}.app; sourceTree = BUILT_PRODUCTS_DIR; }};") L(f'{root_group} /* Root */ = {{', 2)
for path, ref_id in file_refs.items(): L("isa = PBXGroup;", 3)
rel = path.relative_to(ROOT) L("children = (", 3)
lines.append(f"\t\t{ref_id} /* {path.name} */ = {{isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = {path.name}; sourceTree = \"<group>\"; }};") L(f'{sources_group} /* Sources */,', 4)
for path, ref_id in resource_refs.items(): L(f'{resources_group} /* Resources */,', 4)
rel = path.relative_to(ROOT) L(f'{products_group} /* Products */,', 4)
suffix = path.suffix.lower() L(");", 3)
if suffix == ".plist": L('sourceTree = "<group>";', 3)
file_type = "text.plist.xml" L("};", 2)
elif suffix == ".entitlements":
file_type = "text.plist.entitlements"
elif path.name == "Contents.json":
file_type = "text.json"
else:
file_type = "folder.assetcatalog" if path.parent.suffix == ".xcassets" and path.name == "Contents.json" and path.parent.parent.name == "Assets.xcassets" else "text.json"
if path.name == "Contents.json" and "Assets.xcassets" in str(path):
if path.parent == RESOURCE_ROOT / "Assets.xcassets":
continue
if path.parent.name.endswith(".appiconset"):
file_type = "text.json"
elif path.parent == RESOURCE_ROOT / "Assets.xcassets":
file_type = "folder.assetcatalog"
# asset catalog handled as single folder reference below
if "Assets.xcassets" in str(path) and path != RESOURCE_ROOT / "Assets.xcassets":
continue
if path == RESOURCE_ROOT / "Assets.xcassets":
lines.append(f"\t\t{ref_id} /* Assets.xcassets */ = {{isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; }};")
continue
lines.append(f"\t\t{ref_id} /* {path.name} */ = {{isa = PBXFileReference; lastKnownFileType = {file_type}; path = {path.name}; sourceTree = \"<group>\"; }};")
lines.append("/* End PBXFileReference section */\n")
# Simpler approach: rewrite resource refs cleanly # Sources group
resource_file_refs = [] L(f'{sources_group} /* Sources */ = {{', 2)
asset_ref = uuid("asset-catalog") L("isa = PBXGroup;", 3)
info_ref = uuid("info-plist") L("children = (", 3)
ent_ref = uuid("entitlements") for f in top_files:
resource_file_refs = [ L(f'{f_uuids[f]} /* {f} */,', 4)
(RESOURCE_ROOT / "Assets.xcassets", asset_ref, "folder.assetcatalog", "Assets.xcassets"), for d in sorted(top_dirs):
(RESOURCE_ROOT / "Info.plist", info_ref, "text.plist.xml", "Info.plist"), L(f'{group_uuids[d]} /* {d} */,', 4)
(RESOURCE_ROOT / "Bugger.entitlements", ent_ref, "text.plist.entitlements", "Bugger.entitlements"), L(");", 3)
] L("path = Sources;", 3)
L('sourceTree = "<group>";', 3)
L("};", 2)
lines = [] # Subdirectory groups
lines.append("// !$*UTF8*$!") for g in sorted_groups:
lines.append("{") name = g.split("/")[-1]
lines.append("\tarchiveVersion = 1;") L(f'{group_uuids[g]} /* {name} */ = {{', 2)
lines.append("\tclasses = {};") L("isa = PBXGroup;", 3)
lines.append("\tobjectVersion = 56;") L("children = (", 3)
lines.append("\tobjects = {") for f in dir_files(g):
L(f'{f_uuids[f]} /* {f.split("/")[-1]} */,', 4)
for sg in subdirs(g):
L(f'{group_uuids[sg]} /* {sg.split("/")[-1]} */,', 4)
L(");", 3)
L(f"path = {name};", 3)
L('sourceTree = "<group>";', 3)
L("};", 2)
lines.append("\n/* Begin PBXBuildFile section */") # Resources group
swift_build_entries = [] L(f'{resources_group} /* Resources */ = {{', 2)
for path in swift_files: L("isa = PBXGroup;", 3)
ref_id = uuid(f"file-{path}") L("children = (", 3)
build_id = uuid(f"build-{path}") L(f'{asset_ref} /* Assets.xcassets */,', 4)
swift_build_entries.append((build_id, ref_id, path.name)) L(f'{info_ref} /* Info.plist */,', 4)
lines.append(f"\t\t{build_id} /* {path.name} in Sources */ = {{isa = PBXBuildFile; fileRef = {ref_id}; }};") L(f'{ent_ref} /* Bugger.entitlements */,', 4)
L(");", 3)
L("path = Resources;", 3)
L('sourceTree = "<group>";', 3)
L("};", 2)
asset_build_id = uuid("asset-build") # Products group
lines.append(f"\t\t{asset_build_id} /* Assets.xcassets in Resources */ = {{isa = PBXBuildFile; fileRef = {asset_ref}; }};") L(f'{products_group} /* Products */ = {{', 2)
lines.append("/* End PBXBuildFile section */\n") L("isa = PBXGroup;", 3)
L("children = (", 3)
L(f'{product_ref} /* {PROJECT_NAME}.app */,', 4)
L(");", 3)
L("name = Products;", 3)
L('sourceTree = "<group>";', 3)
L("};", 2)
lines.append("/* Begin PBXFileReference section */") L("/* End PBXGroup section */")
lines.append(f"\t\t{product_ref_id} /* {PROJECT_NAME}.app */ = {{isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = {PROJECT_NAME}.app; sourceTree = BUILT_PRODUCTS_DIR; }};") L()
swift_ref_entries = []
for path in swift_files:
ref_id = uuid(f"file-{path}")
swift_ref_entries.append((ref_id, path))
lines.append(f"\t\t{ref_id} /* {path.name} */ = {{isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = {path.name}; sourceTree = \"<group>\"; }};")
lines.append(f"\t\t{asset_ref} /* Assets.xcassets */ = {{isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; }};")
lines.append(f"\t\t{info_ref} /* Info.plist */ = {{isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; }};")
lines.append(f"\t\t{ent_ref} /* Bugger.entitlements */ = {{isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Bugger.entitlements; sourceTree = \"<group>\"; }};")
lines.append("/* End PBXFileReference section */\n")
def render_groups(): # -- PBXNativeTarget --
group_defs = [] L("/* Begin PBXNativeTarget section */")
L(f'{target_id} /* {PROJECT_NAME} */ = {{', 2)
L("isa = PBXNativeTarget;", 3)
L(f'buildConfigurationList = {target_cfg_list} /* Build configuration list for PBXNativeTarget "{PROJECT_NAME}" */;', 3)
L("buildPhases = (", 3)
L(f'{sources_phase} /* Sources */,', 4)
L(f'{frameworks_phase} /* Frameworks */,', 4)
L(f'{resources_phase} /* Resources */,', 4)
L(");", 3)
L("buildRules = (", 3)
L(");", 3)
L("dependencies = (", 3)
L(");", 3)
L(f"name = {PROJECT_NAME};", 3)
L(f"productName = {PROJECT_NAME};", 3)
L(f"productReference = {product_ref} /* {PROJECT_NAME}.app */;", 3)
L('productType = "com.apple.product-type.application";', 3)
L("};", 2)
L("/* End PBXNativeTarget section */")
L()
def walk(dir_path: Path, group_name: str, group_id: str, parent_set: list[str]): # -- PBXProject --
children = [] L("/* Begin PBXProject section */")
for child in sorted(dir_path.iterdir()): L(f'{project_id} /* Project object */ = {{', 2)
if child.is_dir() and child.name != ".DS_Store": L("isa = PBXProject;", 3)
child_id = uuid(f"group-{child}") L("attributes = {", 3)
children.append(child_id) L("BuildIndependentTargetsInParallel = 1;", 4)
walk(child, child.name, child_id, []) L("LastSwiftUpdateCheck = 2600;", 4)
elif child.suffix == ".swift": L("LastUpgradeCheck = 2600;", 4)
ref_id = uuid(f"file-{child}") L(f"TargetAttributes = {{", 4)
children.append(ref_id) L(f"{target_id} = {{", 5)
if dir_path == RESOURCE_ROOT: L("CreatedOnToolsVersion = 26.0;", 6)
children = [asset_ref, info_ref, ent_ref] L("};", 5)
if dir_path == ROOT: L("};", 4)
children = [sources_group_id, resources_group_id, products_group_id] L("};", 3)
if dir_path == SOURCE_ROOT: L(f'buildConfigurationList = {project_cfg_list} /* Build configuration list for PBXProject "{PROJECT_NAME}" */;', 3)
children = [] L('compatibilityVersion = "Xcode 14.0";', 3)
for child in sorted(dir_path.iterdir()): L("developmentRegion = en;", 3)
if child.is_dir(): L("hasScannedForEncodings = 0;", 3)
children.append(uuid(f"group-{child}")) L("knownRegions = (", 3)
elif child.suffix == ".swift": L("en,", 4)
children.append(uuid(f"file-{child}")) L("Base,", 4)
child_lines = "\n".join([f"\t\t\t\t{id_}," for id_ in children]) L(");", 3)
path_line = f"path = {group_name};" if group_name not in ("", PROJECT_NAME) else "" L(f"mainGroup = {root_group};", 3)
if group_id == main_group_id: L(f"productRefGroup = {products_group} /* Products */;", 3)
group_defs.append( L('projectDirPath = "";', 3)
f"\t\t{group_id} = {{\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n{child_lines}\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t}};" L('projectRoot = "";', 3)
) L("targets = (", 3)
elif group_id == products_group_id: L(f'{target_id} /* {PROJECT_NAME} */,', 4)
group_defs.append( L(");", 3)
f"\t\t{group_id} = {{\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t{product_ref_id},\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t}};" L("};", 2)
) L("/* End PBXProject section */")
elif group_id == sources_group_id: L()
group_defs.append(
f"\t\t{group_id} = {{\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n{child_lines}\n\t\t\t);\n\t\t\tpath = Sources;\n\t\t\tsourceTree = \"<group>\";\n\t\t}};"
)
elif group_id == resources_group_id:
group_defs.append(
f"\t\t{group_id} = {{\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t{asset_ref},\n\t\t\t\t{info_ref},\n\t\t\t\t{ent_ref},\n\t\t\t);\n\t\t\tpath = Resources;\n\t\t\tsourceTree = \"<group>\";\n\t\t}};"
)
else:
rel = dir_path.relative_to(SOURCE_ROOT) if str(dir_path).startswith(str(SOURCE_ROOT)) else dir_path.name
group_defs.append(
f"\t\t{group_id} = {{\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n{child_lines}\n\t\t\t);\n\t\t\tpath = {dir_path.name};\n\t\t\tsourceTree = \"<group>\";\n\t\t}};"
)
walk(ROOT, PROJECT_NAME, main_group_id, []) # -- PBXResourcesBuildPhase --
for child in sorted(SOURCE_ROOT.iterdir()): L("/* Begin PBXResourcesBuildPhase section */")
if child.is_dir(): L(f'{resources_phase} /* Resources */ = {{', 2)
walk(child, child.name, uuid(f"group-{child}"), []) L("isa = PBXResourcesBuildPhase;", 3)
return "\n".join(group_defs) L("buildActionMask = 2147483647;", 3)
L("files = (", 3)
L(f'{asset_build} /* Assets.xcassets in Resources */,', 4)
L(");", 3)
L("runOnlyForDeploymentPostprocessing = 0;", 3)
L("};", 2)
L("/* End PBXResourcesBuildPhase section */")
L()
lines.append("/* Begin PBXFrameworksBuildPhase section */") # -- PBXSourcesBuildPhase --
lines.append(f"\t\t{frameworks_phase_id} /* Frameworks */ = {{") L("/* Begin PBXSourcesBuildPhase section */")
lines.append("\t\t\tisa = PBXFrameworksBuildPhase;") L(f'{sources_phase} /* Sources */ = {{', 2)
lines.append("\t\t\tbuildActionMask = 2147483647;") L("isa = PBXSourcesBuildPhase;", 3)
lines.append("\t\t\tfiles = (") L("buildActionMask = 2147483647;", 3)
lines.append("\t\t\t);") L("files = (", 3)
lines.append("\t\t\trunOnlyForDeploymentPostprocessing = 0;") for f in sorted_files:
lines.append("\t\t};") name = f.split("/")[-1]
lines.append("/* End PBXFrameworksBuildPhase section */\n") L(f'{b_uuids[f]} /* {name} in Sources */,', 4)
L(");", 3)
L("runOnlyForDeploymentPostprocessing = 0;", 3)
L("};", 2)
L("/* End PBXSourcesBuildPhase section */")
L()
lines.append("/* Begin PBXGroup section */") # -- XCBuildConfiguration --
lines.append(render_groups()) L("/* Begin XCBuildConfiguration section */")
lines.append("/* End PBXGroup section */\n")
lines.append("/* Begin PBXNativeTarget section */") # Project Debug
lines.append(f"\t\t{target_id} /* {PROJECT_NAME} */ = {{") L(f'{project_dbg} /* Debug */ = {{', 2)
lines.append("\t\t\tisa = PBXNativeTarget;") L("isa = XCBuildConfiguration;", 3)
lines.append(f"\t\t\tbuildConfigurationList = {target_config_list_id} /* Build configuration list for PBXNativeTarget \"{PROJECT_NAME}\" */;") L("buildSettings = {", 3)
lines.append("\t\t\tbuildPhases = (") L("ALWAYS_SEARCH_USER_PATHS = NO;", 4)
lines.append(f"\t\t\t\t{sources_phase_id} /* Sources */,") L("CLANG_ENABLE_MODULES = YES;", 4)
lines.append(f"\t\t\t\t{frameworks_phase_id} /* Frameworks */,") L("DEBUG_INFORMATION_FORMAT = dwarf;", 4)
lines.append(f"\t\t\t\t{resources_phase_id} /* Resources */,") L("SDKROOT = macosx;", 4)
lines.append("\t\t\t);") L("SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;", 4)
lines.append("\t\t\tbuildRules = (") L('SWIFT_OPTIMIZATION_LEVEL = "-Onone";', 4)
lines.append("\t\t\t);") L("};", 3)
lines.append("\t\t\tdependencies = (") L("name = Debug;", 3)
lines.append("\t\t\t);") L("};", 2)
lines.append(f"\t\t\tname = {PROJECT_NAME};")
lines.append(f"\t\t\tproductName = {PROJECT_NAME};")
lines.append(f"\t\t\tproductReference = {product_ref_id} /* {PROJECT_NAME}.app */;")
lines.append("\t\t\tproductType = \"com.apple.product-type.application\";")
lines.append("\t\t};")
lines.append("/* End PBXNativeTarget section */\n")
lines.append("/* Begin PBXProject section */") # Project Release
lines.append(f"\t\t{project_id} /* Project object */ = {{") L(f'{project_rel} /* Release */ = {{', 2)
lines.append("\t\t\tisa = PBXProject;") L("isa = XCBuildConfiguration;", 3)
lines.append("\t\t\tattributes = {") L("buildSettings = {", 3)
lines.append("\t\t\t\tBuildIndependentTargetsInParallel = 1;") L("ALWAYS_SEARCH_USER_PATHS = NO;", 4)
lines.append("\t\t\t\tLastSwiftUpdateCheck = 1500;") L("CLANG_ENABLE_MODULES = YES;", 4)
lines.append("\t\t\t\tLastUpgradeCheck = 1500;") L('DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";', 4)
lines.append("\t\t\t\tTargetAttributes = {") L("SDKROOT = macosx;", 4)
lines.append(f"\t\t\t\t\t{target_id} = {{") L("SWIFT_COMPILATION_MODE = wholemodule;", 4)
lines.append("\t\t\t\t\t\tCreatedOnToolsVersion = 15.0;") L("};", 3)
lines.append("\t\t\t\t\t};") L("name = Release;", 3)
lines.append("\t\t\t\t};") L("};", 2)
lines.append("\t\t\t};")
lines.append(f"\t\t\tbuildConfigurationList = {project_config_list_id} /* Build configuration list for PBXProject \"{PROJECT_NAME}\" */;")
lines.append("\t\t\tcompatibilityVersion = \"Xcode 14.0\";")
lines.append("\t\t\tdevelopmentRegion = en;")
lines.append("\t\t\thasScannedForEncodings = 0;")
lines.append("\t\t\tknownRegions = (")
lines.append("\t\t\t\ten,")
lines.append("\t\t\t\tBase,")
lines.append("\t\t\t);")
lines.append(f"\t\t\tmainGroup = {main_group_id};")
lines.append(f"\t\t\tproductRefGroup = {products_group_id} /* Products */;")
lines.append("\t\t\tprojectDirPath = \"\";")
lines.append("\t\t\tprojectRoot = \"\";")
lines.append("\t\t\ttargets = (")
lines.append(f"\t\t\t\t{target_id} /* {PROJECT_NAME} */,")
lines.append("\t\t\t);")
lines.append("\t\t};")
lines.append("/* End PBXProject section */\n")
lines.append("/* Begin PBXResourcesBuildPhase section */") # Target Debug
lines.append(f"\t\t{resources_phase_id} /* Resources */ = {{") target_settings = [
lines.append("\t\t\tisa = PBXResourcesBuildPhase;")
lines.append("\t\t\tbuildActionMask = 2147483647;")
lines.append("\t\t\tfiles = (")
lines.append(f"\t\t\t\t{asset_build_id} /* Assets.xcassets in Resources */,")
lines.append("\t\t\t);")
lines.append("\t\t\trunOnlyForDeploymentPostprocessing = 0;")
lines.append("\t\t};")
lines.append("/* End PBXResourcesBuildPhase section */\n")
lines.append("/* Begin PBXSourcesBuildPhase section */")
lines.append(f"\t\t{sources_phase_id} /* Sources */ = {{")
lines.append("\t\t\tisa = PBXSourcesBuildPhase;")
lines.append("\t\t\tbuildActionMask = 2147483647;")
lines.append("\t\t\tfiles = (")
for build_id, _, name in swift_build_entries:
lines.append(f"\t\t\t\t{build_id} /* {name} in Sources */,")
lines.append("\t\t\t);")
lines.append("\t\t\trunOnlyForDeploymentPostprocessing = 0;")
lines.append("\t\t};")
lines.append("/* End PBXSourcesBuildPhase section */\n")
common_settings = [
("ASSETCATALOG_COMPILER_APPICON_NAME", "AppIcon"), ("ASSETCATALOG_COMPILER_APPICON_NAME", "AppIcon"),
("CODE_SIGN_ENTITLEMENTS", "Resources/Bugger.entitlements"), ("CODE_SIGN_ENTITLEMENTS", "Resources/Bugger.entitlements"),
("CODE_SIGN_STYLE", "Automatic"), ("CODE_SIGN_STYLE", "Automatic"),
("COMBINE_HIDPI_IMAGES", "YES"), ("COMBINE_HIDPI_IMAGES", "YES"),
("CURRENT_PROJECT_VERSION", "1"), ("CURRENT_PROJECT_VERSION", "1"),
("DEVELOPMENT_TEAM", ""), ("DEVELOPMENT_TEAM", '""'),
("ENABLE_HARDENED_RUNTIME", "YES"), ("ENABLE_HARDENED_RUNTIME", "YES"),
("FEISHU_APP_ID", ""), ("FEISHU_APP_ID", '"$(FEISHU_APP_ID)"'),
("FEISHU_APP_SECRET", ""), ("FEISHU_APP_SECRET", '"$(FEISHU_APP_SECRET)"'),
("FEISHU_BASE_DOMAIN", "xorbitlab.feishu.cn"), ("FEISHU_BASE_DOMAIN", '"xorbitlab.feishu.cn"'),
("GENERATE_INFOPLIST_FILE", "NO"), ("GENERATE_INFOPLIST_FILE", "NO"),
("INFOPLIST_FILE", "Resources/Info.plist"), ("INFOPLIST_FILE", "Resources/Info.plist"),
("LD_RUNPATH_SEARCH_PATHS", "$(inherited) @executable_path/../Frameworks"), ("LD_RUNPATH_SEARCH_PATHS",
'"$(inherited) @executable_path/../Frameworks"'),
("MACOSX_DEPLOYMENT_TARGET", "14.0"), ("MACOSX_DEPLOYMENT_TARGET", "14.0"),
("MARKETING_VERSION", "1.0"), ("MARKETING_VERSION", "1.0"),
("PRODUCT_BUNDLE_IDENTIFIER", BUNDLE_ID), ("PRODUCT_BUNDLE_IDENTIFIER", BUNDLE_ID),
("PRODUCT_NAME", "$(TARGET_NAME)"), ("PRODUCT_NAME", '"$(TARGET_NAME)"'),
("SWIFT_EMIT_LOC_STRINGS", "YES"), ("SWIFT_EMIT_LOC_STRINGS", "YES"),
("SWIFT_VERSION", "5.0"), ("SWIFT_VERSION", "5.0"),
] ]
def config_block(config_id: str, name: str, settings: list[tuple[str, str]]) -> str: L(f'{target_dbg} /* Debug */ = {{', 2)
setting_lines = "\n".join([f"\t\t\t\t{key} = {value};" for key, value in settings]) L("isa = XCBuildConfiguration;", 3)
return ( L("buildSettings = {", 3)
f"\t\t{config_id} /* {name} */ = {{\n" for k, v in target_settings:
f"\t\t\tisa = XCBuildConfiguration;\n" L(f"{k} = {v};", 4)
f"\t\t\tbuildSettings = {{\n{setting_lines}\n" L("};", 3)
f"\t\t\t}};\n" L("name = Debug;", 3)
f"\t\t\tname = {name};\n" L("};", 2)
f"\t\t}};"
)
lines.append("/* Begin XCBuildConfiguration section */") # Target Release
lines.append(config_block(debug_config_id, "Debug", [("ALWAYS_SEARCH_USER_PATHS", "NO"), ("CLANG_ENABLE_MODULES", "YES"), ("COPY_PHASE_STRIP", "NO"), ("DEBUG_INFORMATION_FORMAT", "dwarf"), ("GCC_DYNAMIC_NO_PIC", "NO"), ("GCC_OPTIMIZATION_LEVEL", "0"), ("ONLY_ACTIVE_ARCH", "YES"), ("SDKROOT", "macosx"), ("SWIFT_ACTIVE_COMPILATION_CONDITIONS", "DEBUG"), ("SWIFT_OPTIMIZATION_LEVEL", "-Onone")])) L(f'{target_rel} /* Release */ = {{', 2)
lines.append(config_block(release_config_id, "Release", [("ALWAYS_SEARCH_USER_PATHS", "NO"), ("CLANG_ENABLE_MODULES", "YES"), ("COPY_PHASE_STRIP", "NO"), ("DEBUG_INFORMATION_FORMAT", "dwarf-with-dsym"), ("SDKROOT", "macosx"), ("SWIFT_COMPILATION_MODE", "wholemodule")])) L("isa = XCBuildConfiguration;", 3)
lines.append(config_block(target_debug_config_id, "Debug", common_settings)) L("buildSettings = {", 3)
lines.append(config_block(target_release_config_id, "Release", common_settings)) for k, v in target_settings:
lines.append("/* End XCBuildConfiguration section */\n") L(f"{k} = {v};", 4)
L("};", 3)
L("name = Release;", 3)
L("};", 2)
lines.append("/* Begin XCConfigurationList section */") L("/* End XCBuildConfiguration section */")
lines.append(f"\t\t{project_config_list_id} /* Build configuration list for PBXProject \"{PROJECT_NAME}\" */ = {{") L()
lines.append("\t\t\tisa = XCConfigurationList;")
lines.append("\t\t\tbuildConfigurations = (") # -- XCConfigurationList --
lines.append(f"\t\t\t\t{debug_config_id} /* Debug */,") L("/* Begin XCConfigurationList section */")
lines.append(f"\t\t\t\t{release_config_id} /* Release */,") L(f'{target_cfg_list} /* Build configuration list for PBXNativeTarget "{PROJECT_NAME}" */ = {{', 2)
lines.append("\t\t\t);") L("isa = XCConfigurationList;", 3)
lines.append("\t\t\tdefaultConfigurationIsVisible = 0;") L("buildConfigurations = (", 3)
lines.append("\t\t\tdefaultConfigurationName = Release;") L(f'{target_dbg} /* Debug */,', 4)
lines.append("\t\t};") L(f'{target_rel} /* Release */,', 4)
lines.append(f"\t\t{target_config_list_id} /* Build configuration list for PBXNativeTarget \"{PROJECT_NAME}\" */ = {{") L(");", 3)
lines.append("\t\t\tisa = XCConfigurationList;") L("defaultConfigurationIsVisible = 0;", 3)
lines.append("\t\t\tbuildConfigurations = (") L("defaultConfigurationName = Release;", 3)
lines.append(f"\t\t\t\t{target_debug_config_id} /* Debug */,") L("};", 2)
lines.append(f"\t\t\t\t{target_release_config_id} /* Release */,") L(f'{project_cfg_list} /* Build configuration list for PBXProject "{PROJECT_NAME}" */ = {{', 2)
lines.append("\t\t\t);") L("isa = XCConfigurationList;", 3)
lines.append("\t\t\tdefaultConfigurationIsVisible = 0;") L("buildConfigurations = (", 3)
lines.append("\t\t\tdefaultConfigurationName = Release;") L(f'{project_dbg} /* Debug */,', 4)
lines.append("\t\t};") L(f'{project_rel} /* Release */,', 4)
lines.append("/* End XCConfigurationList section */") L(");", 3)
lines.append("\t};") L("defaultConfigurationIsVisible = 0;", 3)
lines.append(f"\trootObject = {project_id} /* Project object */;") L("defaultConfigurationName = Release;", 3)
lines.append("}") L("};", 2)
return "\n".join(lines) L("/* End XCConfigurationList section */")
L()
L("};", 1)
L(f"rootObject = {project_id} /* Project object */;", 1)
L("}")
return "\n".join(b) + "\n"
def main(): def build_xcscheme() -> str:
project_dir = ROOT / f"{PROJECT_NAME}.xcodeproj" target_id = uuid("target")
project_dir.mkdir(exist_ok=True) return f"""<?xml version="1.0" encoding="UTF-8"?>
(project_dir / "project.pbxproj").write_text(pbxproj())
scheme_dir = project_dir / "xcshareddata" / "xcschemes"
scheme_dir.mkdir(parents=True, exist_ok=True)
(scheme_dir / f"{PROJECT_NAME}.xcscheme").write_text(f"""<?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1500" LastUpgradeVersion = "2600"
version = "1.7"> version = "1.7">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@ -434,7 +427,7 @@ def main():
buildForAnalyzing = "YES"> buildForAnalyzing = "YES">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "{uuid('target')}" BlueprintIdentifier = "{target_id}"
BuildableName = "{PROJECT_NAME}.app" BuildableName = "{PROJECT_NAME}.app"
BlueprintName = "{PROJECT_NAME}" BlueprintName = "{PROJECT_NAME}"
ReferencedContainer = "container:{PROJECT_NAME}.xcodeproj"> ReferencedContainer = "container:{PROJECT_NAME}.xcodeproj">
@ -456,7 +449,7 @@ def main():
runnableDebuggingMode = "0"> runnableDebuggingMode = "0">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "{uuid('target')}" BlueprintIdentifier = "{target_id}"
BuildableName = "{PROJECT_NAME}.app" BuildableName = "{PROJECT_NAME}.app"
BlueprintName = "{PROJECT_NAME}" BlueprintName = "{PROJECT_NAME}"
ReferencedContainer = "container:{PROJECT_NAME}.xcodeproj"> ReferencedContainer = "container:{PROJECT_NAME}.xcodeproj">
@ -464,8 +457,37 @@ def main():
</BuildableProductRunnable> </BuildableProductRunnable>
</LaunchAction> </LaunchAction>
</Scheme> </Scheme>
""") """
print(f"Generated {project_dir}")
def main():
PROJECT_DIR.mkdir(parents=True, exist_ok=True)
# pbxproj
pbxproj = build_pbxproj()
(PROJECT_DIR / "project.pbxproj").write_text(pbxproj)
print(f"Wrote {PROJECT_DIR / 'project.pbxproj'} ({len(pbxproj)} bytes)")
# Workspace
ws_dir = PROJECT_DIR / "project.xcworkspace"
ws_dir.mkdir(parents=True, exist_ok=True)
(ws_dir / "contents.xcworkspacedata").write_text(
'<?xml version="1.0" encoding="UTF-8"?>\n'
'<Workspace\n'
' version = "1.0">\n'
' <FileRef\n'
' location = "self:">\n'
' </FileRef>\n'
'</Workspace>\n'
)
# Scheme
scheme_dir = PROJECT_DIR / "xcshareddata" / "xcschemes"
scheme_dir.mkdir(parents=True, exist_ok=True)
(scheme_dir / f"{PROJECT_NAME}.xcscheme").write_text(build_xcscheme())
print("Generated project successfully.")
print(f" Sources: {len(collect_swift_files())} Swift files")
if __name__ == "__main__": if __name__ == "__main__":