@@ -18,7 +18,14 @@ function getElectronVersion(): string {
1818 return target ;
1919}
2020
21- function getEntitlementsForFile ( filePath : string ) : string {
21+ const mainProvisioningProfilePath = path . join ( baseDir , 'darwin' , 'main.provisionprofile' ) ;
22+ const agentsProvisioningProfilePath = path . join ( baseDir , 'darwin' , 'agents.provisionprofile' ) ;
23+
24+ function hasProvisioningProfile ( ) : boolean {
25+ return fs . existsSync ( mainProvisioningProfilePath ) ;
26+ }
27+
28+ function getEntitlementsForFile ( filePath : string , tempDir : string , useProvisioningProfile : boolean , teamId ?: string ) : string {
2229 if ( filePath . includes ( ' Helper (GPU).app' ) ) {
2330 return path . join ( baseDir , 'azure-pipelines' , 'darwin' , 'helper-gpu-entitlements.plist' ) ;
2431 } else if ( filePath . includes ( ' Helper (Renderer).app' ) ) {
@@ -28,7 +35,51 @@ function getEntitlementsForFile(filePath: string): string {
2835 } else if ( filePath . includes ( ' Helper.app' ) ) {
2936 return path . join ( baseDir , 'azure-pipelines' , 'darwin' , 'helper-entitlements.plist' ) ;
3037 }
31- return path . join ( baseDir , 'azure-pipelines' , 'darwin' , 'app-entitlements.plist' ) ;
38+ const entitlementsPath = path . join ( baseDir , 'azure-pipelines' , 'darwin' , 'app-entitlements.plist' ) ;
39+ if ( ! useProvisioningProfile ) {
40+ // Without a provisioning profile, keychain-access-groups entitlement
41+ // will cause signing failures. Strip it from the entitlements plist.
42+ return getStrippedEntitlements ( entitlementsPath , tempDir ) ;
43+ }
44+ if ( teamId ) {
45+ return getExpandedEntitlements ( entitlementsPath , tempDir , teamId ) ;
46+ }
47+ return entitlementsPath ;
48+ }
49+
50+ let _strippedEntitlementsPath : string | undefined ;
51+
52+ /**
53+ * Returns a path to a copy of the entitlements plist with the
54+ * keychain-access-groups key removed.
55+ */
56+ function getStrippedEntitlements ( entitlementsPath : string , tempDir : string ) : string {
57+ if ( ! _strippedEntitlementsPath ) {
58+ const content = fs . readFileSync ( entitlementsPath , 'utf8' ) ;
59+ const stripped = content . replace (
60+ / \s * < k e y > k e y c h a i n - a c c e s s - g r o u p s < \/ k e y > \s * < a r r a y > [ \s \S ] * ?< \/ a r r a y > / ,
61+ ''
62+ ) ;
63+ _strippedEntitlementsPath = path . join ( tempDir , 'app-entitlements-stripped.plist' ) ;
64+ fs . writeFileSync ( _strippedEntitlementsPath , stripped ) ;
65+ }
66+ return _strippedEntitlementsPath ;
67+ }
68+
69+ let expandedEntitlementsPath : string | undefined ;
70+
71+ /**
72+ * Returns a path to a copy of the entitlements plist with
73+ * $(TeamIdentifierPrefix) expanded to the actual team identifier.
74+ */
75+ function getExpandedEntitlements ( entitlementsPath : string , tempDir : string , teamId : string ) : string {
76+ if ( ! expandedEntitlementsPath ) {
77+ const content = fs . readFileSync ( entitlementsPath , 'utf8' ) ;
78+ const expanded = content . replace ( / \$ \( T e a m I d e n t i f i e r P r e f i x \) / g, teamId + '.' ) ;
79+ expandedEntitlementsPath = path . join ( tempDir , 'app-entitlements.plist' ) ;
80+ fs . writeFileSync ( expandedEntitlementsPath , expanded ) ;
81+ }
82+ return expandedEntitlementsPath ;
3283}
3384
3485async function retrySignOnKeychainError < T > ( fn : ( ) => Promise < T > , maxRetries : number = 3 ) : Promise < T > {
@@ -60,7 +111,7 @@ async function retrySignOnKeychainError<T>(fn: () => Promise<T>, maxRetries: num
60111 throw lastError ;
61112}
62113
63- async function main ( buildDir ?: string ) : Promise < void > {
114+ async function main ( buildDir ?: string , skipProvisioningProfile ?: boolean ) : Promise < void > {
64115 const tempDir = process . env [ 'AGENT_TEMPDIRECTORY' ] ;
65116 const arch = process . env [ 'VSCODE_ARCH' ] ;
66117 const identity = process . env [ 'CODESIGN_IDENTITY' ] ;
@@ -80,23 +131,51 @@ async function main(buildDir?: string): Promise<void> {
80131 ? path . resolve ( appRoot , appName , 'Contents' , 'Applications' , `${ product . embedded . nameLong } .app` , 'Contents' , 'Info.plist' )
81132 : undefined ;
82133
134+ const useProvisioningProfile = ! skipProvisioningProfile && hasProvisioningProfile ( ) ;
135+ const resolvedProvisioningProfile = useProvisioningProfile ? mainProvisioningProfilePath : undefined ;
136+
137+ let teamId : string | undefined ;
138+ if ( resolvedProvisioningProfile ) {
139+ const profilePlist = await spawn ( 'security' , [ 'cms' , '-D' , '-i' , resolvedProvisioningProfile ] ) ;
140+ const teamIdMatch = / < k e y > T e a m I d e n t i f i e r < \/ k e y > \s * < a r r a y > \s * < s t r i n g > ( .* ?) < \/ s t r i n g > / s. exec ( profilePlist ) ;
141+ if ( teamIdMatch ) {
142+ teamId = teamIdMatch [ 1 ] ;
143+ console . log ( `Extracted TeamIdentifier from provisioning profile: ${ teamId } ` ) ;
144+ } else {
145+ console . warn ( 'Could not extract TeamIdentifier from provisioning profile; $(TeamIdentifierPrefix) will not be expanded' ) ;
146+ }
147+ }
148+
149+ // Embed the agents provisioning profile into the embedded app bundle
150+ // before signing, since @electron/osx-sign only supports one top-level profile.
151+ if ( useProvisioningProfile && product . embedded && fs . existsSync ( agentsProvisioningProfilePath ) ) {
152+ const embeddedAppPath = path . join ( appRoot , appName , 'Contents' , 'Applications' , `${ product . embedded . nameLong } .app` ) ;
153+ if ( fs . existsSync ( embeddedAppPath ) ) {
154+ const embeddedProfileDest = path . join ( embeddedAppPath , 'Contents' , 'embedded.provisionprofile' ) ;
155+ fs . copyFileSync ( agentsProvisioningProfilePath , embeddedProfileDest ) ;
156+ console . log ( `Embedded agents provisioning profile into ${ embeddedProfileDest } ` ) ;
157+ }
158+ }
159+
83160 const appOpts : SignOptions = {
84161 app : path . join ( appRoot , appName ) ,
85162 platform : 'darwin' ,
86163 optionsForFile : ( filePath ) => ( {
87- entitlements : getEntitlementsForFile ( filePath ) ,
164+ entitlements : getEntitlementsForFile ( filePath , tempDir , useProvisioningProfile , teamId ) ,
88165 hardenedRuntime : true ,
89166 } ) ,
90167 preAutoEntitlements : false ,
91- preEmbedProvisioningProfile : false ,
168+ preEmbedProvisioningProfile : ! ! resolvedProvisioningProfile ,
169+ provisioningProfile : resolvedProvisioningProfile ,
92170 keychain : path . join ( tempDir , 'buildagent.keychain' ) ,
93171 version : getElectronVersion ( ) ,
94172 identity,
95173 } ;
96174
97175 // Only overwrite plist entries for x64 and arm64 builds,
98176 // universal will get its copy from the x64 build.
99- if ( arch !== 'universal' ) {
177+ // Skip when re-signing (skipProvisioningProfile) since entries already exist.
178+ if ( arch !== 'universal' && ! skipProvisioningProfile ) {
100179 await spawn ( 'plutil' , [
101180 '-insert' ,
102181 'NSAppleEventsUsageDescription' ,
@@ -176,7 +255,10 @@ async function main(buildDir?: string): Promise<void> {
176255}
177256
178257if ( import . meta. main ) {
179- main ( process . argv [ 2 ] ) . catch ( async err => {
258+ const args = process . argv . slice ( 2 ) ;
259+ const skipProvisioningProfile = args . includes ( '--skip-provisioning-profile' ) ;
260+ const buildDir = args . filter ( a => ! a . startsWith ( '--' ) ) [ 0 ] ;
261+ main ( buildDir , skipProvisioningProfile ) . catch ( async err => {
180262 console . error ( err ) ;
181263 const tempDir = process . env [ 'AGENT_TEMPDIRECTORY' ] ;
182264 if ( tempDir ) {
0 commit comments