@@ -49,54 +49,23 @@ const BackendPath = "/ide-desktop/backend"
49
49
const ProductInfoPath = BackendPath + "/product-info.json"
50
50
51
51
type LaunchContext struct {
52
- alias string
52
+ startTime time.Time
53
+
54
+ port string
55
+ alias string
56
+ label string
57
+
58
+ info * ProductInfo
59
+ backendVersion * version.Version
60
+ wsInfo * supervisor.WorkspaceInfoResponse
61
+
62
+ vmOptionsFile string
53
63
projectDir string
54
64
configDir string
55
65
systemDir string
56
66
projectConfigDir string
57
67
projectContextDir string
58
68
riderSolutionFile string
59
- wsInfo * supervisor.WorkspaceInfoResponse
60
- }
61
-
62
- // TODO(andreafalzetti): remove dir scanning once this is implemented https://youtrack.jetbrains.com/issue/GTW-2402/Rider-Open-Project-dialog-not-displaying-in-remote-dev
63
- func findRiderSolutionFile (root string ) (string , error ) {
64
- slnRegEx := regexp .MustCompile (`^.+\.sln$` )
65
- projRegEx := regexp .MustCompile (`^.+\.csproj$` )
66
-
67
- var slnFiles []string
68
- var csprojFiles []string
69
-
70
- err := filepath .Walk (root , func (path string , info os.FileInfo , err error ) error {
71
- if err != nil {
72
- return err
73
- } else if slnRegEx .MatchString (info .Name ()) {
74
- slnFiles = append (slnFiles , path )
75
- } else if projRegEx .MatchString (info .Name ()) {
76
- csprojFiles = append (csprojFiles , path )
77
- }
78
- return nil
79
- })
80
-
81
- if err != nil {
82
- return "" , err
83
- }
84
-
85
- if len (slnFiles ) > 0 {
86
- return slnFiles [0 ], nil
87
- } else if len (csprojFiles ) > 0 {
88
- return csprojFiles [0 ], nil
89
- }
90
-
91
- return root , nil
92
- }
93
-
94
- func resolveProjectContextDir (launchCtx * LaunchContext ) string {
95
- if launchCtx .alias == "rider" {
96
- return launchCtx .riderSolutionFile
97
- }
98
-
99
- return launchCtx .projectDir
100
69
}
101
70
102
71
// JB startup entrypoint
@@ -133,81 +102,28 @@ func main() {
133
102
return
134
103
}
135
104
136
- projectDir := wsInfo .GetCheckoutLocation ()
137
- gitpodConfig , err := parseGitpodConfig (projectDir )
138
- if err != nil {
139
- log .WithError (err ).Error ("failed to parse .gitpod.yml" )
140
- }
141
-
142
- // configure vmoptions
143
- idePrefix := alias
144
- if alias == "intellij" {
145
- idePrefix = "idea"
146
- }
147
- // [idea64|goland64|pycharm64|phpstorm64].vmoptions
148
- vmOptionsPath := fmt .Sprintf ("/ide-desktop/backend/bin/%s64.vmoptions" , idePrefix )
149
- err = configureVMOptions (gitpodConfig , alias , vmOptionsPath )
150
- if err != nil {
151
- log .WithError (err ).Error ("failed to configure vmoptions" )
152
- }
153
-
154
- qualifier := os .Getenv ("JETBRAINS_BACKEND_QUALIFIER" )
155
- if qualifier == "stable" {
156
- qualifier = ""
157
- } else {
158
- qualifier = "-" + qualifier
159
- }
160
-
161
- var riderSolutionFile string
162
- if alias == "rider" {
163
- riderSolutionFile , err = findRiderSolutionFile (projectDir )
164
- if err != nil {
165
- log .WithError (err ).Error ("failed to find a rider solution file" )
166
- }
167
- }
168
-
169
- configDir := fmt .Sprintf ("/workspace/.config/JetBrains%s" , qualifier )
170
105
launchCtx := & LaunchContext {
171
- alias : alias ,
172
- wsInfo : wsInfo ,
173
- projectDir : projectDir ,
174
- projectContextDir : projectDir ,
175
- configDir : configDir ,
176
- systemDir : fmt .Sprintf ("/workspace/.cache/JetBrains%s" , qualifier ),
177
- riderSolutionFile : riderSolutionFile ,
178
- }
106
+ startTime : startTime ,
179
107
180
- launchCtx .projectContextDir = resolveProjectContextDir (launchCtx )
181
- launchCtx .projectConfigDir = fmt .Sprintf ("%s/RemoteDev-%s/%s" , configDir , info .ProductCode , strings .ReplaceAll (launchCtx .projectContextDir , "/" , "_" ))
108
+ port : port ,
109
+ alias : alias ,
110
+ label : label ,
182
111
183
- // sync initial options
184
- err = syncOptions (launchCtx )
185
- if err != nil {
186
- log .WithError (err ).Error ("failed to sync initial options" )
112
+ info : info ,
113
+ backendVersion : backendVersion ,
114
+ wsInfo : wsInfo ,
187
115
}
116
+ // we should start serving immediately and postpone launch
117
+ // in order to enable a JB Gateway to connect as soon as possible
118
+ go launch (launchCtx )
119
+ // IMPORTANT: don't put startup logic in serve!!!
120
+ serve (launchCtx )
121
+ }
188
122
189
- // install project plugins
190
- version_2022_1 , _ := version .NewVersion ("2022.1" )
191
- if version_2022_1 .LessThanOrEqual (backendVersion ) {
192
- err = installPlugins (gitpodConfig , launchCtx )
193
- installPluginsCost := time .Now ().Local ().Sub (startTime ).Milliseconds ()
194
- if err != nil {
195
- log .WithError (err ).WithField ("cost" , installPluginsCost ).Error ("installing repo plugins: done" )
196
- } else {
197
- log .WithField ("cost" , installPluginsCost ).Info ("installing repo plugins: done" )
198
- }
199
- }
200
-
201
- // install gitpod plugin
202
- err = linkRemotePlugin ()
203
- if err != nil {
204
- log .WithError (err ).Error ("failed to install gitpod-remote plugin" )
205
- }
206
- go run (launchCtx )
207
-
123
+ func serve (launchCtx * LaunchContext ) {
208
124
debugAgentPrefix := "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:"
209
125
http .HandleFunc ("/debug" , func (w http.ResponseWriter , r * http.Request ) {
210
- options , err := readVMOptions (vmOptionsPath )
126
+ options , err := readVMOptions (launchCtx . vmOptionsFile )
211
127
if err != nil {
212
128
log .WithError (err ).Error ("failed to configure debug agent" )
213
129
http .Error (w , err .Error (), http .StatusInternalServerError )
@@ -243,7 +159,7 @@ func main() {
243
159
options = deduplicateVMOption (options , debugOptions , func (l , r string ) bool {
244
160
return strings .HasPrefix (l , debugAgentPrefix ) && strings .HasPrefix (r , debugAgentPrefix )
245
161
})
246
- err = writeVMOptions (vmOptionsPath , options )
162
+ err = writeVMOptions (launchCtx . vmOptionsFile , options )
247
163
if err != nil {
248
164
log .WithError (err ).Error ("failed to configure debug agent" )
249
165
http .Error (w , err .Error (), http .StatusInternalServerError )
@@ -274,7 +190,7 @@ func main() {
274
190
if backendPort == "" {
275
191
backendPort = defaultBackendPort
276
192
}
277
- jsonLink , err := resolveGatewayLink (backendPort , wsInfo )
193
+ jsonLink , err := resolveGatewayLink (backendPort , launchCtx . wsInfo )
278
194
if err != nil {
279
195
log .WithError (err ).Error ("cannot resolve gateway link" )
280
196
http .Error (w , err .Error (), http .StatusServiceUnavailable )
@@ -287,23 +203,23 @@ func main() {
287
203
if backendPort == "" {
288
204
backendPort = defaultBackendPort
289
205
}
290
- gatewayLink , err := resolveGatewayLink (backendPort , wsInfo )
206
+ gatewayLink , err := resolveGatewayLink (backendPort , launchCtx . wsInfo )
291
207
if err != nil {
292
208
log .WithError (err ).Error ("cannot resolve gateway link" )
293
209
http .Error (w , err .Error (), http .StatusServiceUnavailable )
294
210
return
295
211
}
296
212
response := make (map [string ]string )
297
213
response ["link" ] = gatewayLink
298
- response ["label" ] = label
214
+ response ["label" ] = launchCtx . label
299
215
response ["clientID" ] = "jetbrains-gateway"
300
- response ["kind" ] = alias
216
+ response ["kind" ] = launchCtx . alias
301
217
w .Header ().Set ("Content-Type" , "application/json" )
302
218
_ = json .NewEncoder (w ).Encode (response )
303
219
})
304
220
305
- fmt .Printf ("Starting status proxy for desktop IDE at port %s\n " , port )
306
- if err := http .ListenAndServe (fmt .Sprintf (":%s" , port ), nil ); err != nil {
221
+ fmt .Printf ("Starting status proxy for desktop IDE at port %s\n " , launchCtx . port )
222
+ if err := http .ListenAndServe (fmt .Sprintf (":%s" , launchCtx . port ), nil ); err != nil {
307
223
log .Fatal (err )
308
224
}
309
225
}
@@ -415,6 +331,76 @@ func resolveWorkspaceInfo(ctx context.Context) (*supervisor.WorkspaceInfoRespons
415
331
return nil , errors .New ("failed with attempt 10 times" )
416
332
}
417
333
334
+ func launch (launchCtx * LaunchContext ) {
335
+ projectDir := launchCtx .wsInfo .GetCheckoutLocation ()
336
+ gitpodConfig , err := parseGitpodConfig (projectDir )
337
+ if err != nil {
338
+ log .WithError (err ).Error ("failed to parse .gitpod.yml" )
339
+ }
340
+
341
+ // configure vmoptions
342
+ idePrefix := launchCtx .alias
343
+ if launchCtx .alias == "intellij" {
344
+ idePrefix = "idea"
345
+ }
346
+ // [idea64|goland64|pycharm64|phpstorm64].vmoptions
347
+ launchCtx .vmOptionsFile = fmt .Sprintf ("/ide-desktop/backend/bin/%s64.vmoptions" , idePrefix )
348
+ err = configureVMOptions (gitpodConfig , launchCtx .alias , launchCtx .vmOptionsFile )
349
+ if err != nil {
350
+ log .WithError (err ).Error ("failed to configure vmoptions" )
351
+ }
352
+
353
+ qualifier := os .Getenv ("JETBRAINS_BACKEND_QUALIFIER" )
354
+ if qualifier == "stable" {
355
+ qualifier = ""
356
+ } else {
357
+ qualifier = "-" + qualifier
358
+ }
359
+
360
+ var riderSolutionFile string
361
+ if launchCtx .alias == "rider" {
362
+ riderSolutionFile , err = findRiderSolutionFile (projectDir )
363
+ if err != nil {
364
+ log .WithError (err ).Error ("failed to find a rider solution file" )
365
+ }
366
+ }
367
+
368
+ configDir := fmt .Sprintf ("/workspace/.config/JetBrains%s" , qualifier )
369
+ launchCtx .projectDir = projectDir
370
+ launchCtx .configDir = configDir
371
+ launchCtx .systemDir = fmt .Sprintf ("/workspace/.cache/JetBrains%s" , qualifier )
372
+ launchCtx .riderSolutionFile = riderSolutionFile
373
+ launchCtx .projectContextDir = resolveProjectContextDir (launchCtx )
374
+ launchCtx .projectConfigDir = fmt .Sprintf ("%s/RemoteDev-%s/%s" , configDir , launchCtx .info .ProductCode , strings .ReplaceAll (launchCtx .projectContextDir , "/" , "_" ))
375
+
376
+ // sync initial options
377
+ err = syncOptions (launchCtx )
378
+ if err != nil {
379
+ log .WithError (err ).Error ("failed to sync initial options" )
380
+ }
381
+
382
+ // install project plugins
383
+ version_2022_1 , _ := version .NewVersion ("2022.1" )
384
+ if version_2022_1 .LessThanOrEqual (launchCtx .backendVersion ) {
385
+ err = installPlugins (gitpodConfig , launchCtx )
386
+ installPluginsCost := time .Now ().Local ().Sub (launchCtx .startTime ).Milliseconds ()
387
+ if err != nil {
388
+ log .WithError (err ).WithField ("cost" , installPluginsCost ).Error ("installing repo plugins: done" )
389
+ } else {
390
+ log .WithField ("cost" , installPluginsCost ).Info ("installing repo plugins: done" )
391
+ }
392
+ }
393
+
394
+ // install gitpod plugin
395
+ err = linkRemotePlugin ()
396
+ if err != nil {
397
+ log .WithError (err ).Error ("failed to install gitpod-remote plugin" )
398
+ }
399
+
400
+ // run backend
401
+ run (launchCtx )
402
+ }
403
+
418
404
func run (launchCtx * LaunchContext ) {
419
405
var args []string
420
406
args = append (args , "run" )
@@ -443,14 +429,24 @@ func run(launchCtx *LaunchContext) {
443
429
os .Exit (cmd .ProcessState .ExitCode ())
444
430
}
445
431
446
- func remoteDevServerCmd (args []string , productContext * LaunchContext ) * exec.Cmd {
447
- cmd := exec .Command (BackendPath + "/bin/remote-dev-server.sh" , args ... )
448
- cmd .Env = os .Environ ()
449
-
450
- // Set default config and system directories under /workspace to preserve between restarts
451
- cmd .Env = append (cmd .Env ,
452
- fmt .Sprintf ("IJ_HOST_CONFIG_BASE_DIR=%s" , productContext .configDir ),
453
- fmt .Sprintf ("IJ_HOST_SYSTEM_BASE_DIR=%s" , productContext .systemDir ),
432
+ func remoteDevServerCmd (args []string , launchCtx * LaunchContext ) * exec.Cmd {
433
+ shell := os .Getenv ("SHELL" )
434
+ if shell == "" {
435
+ shell = "/bin/bash"
436
+ }
437
+ // Emulate the interactive login shell to ensure that all users defined shell scripts are loaded
438
+ shellArgs := []string {"-ilc" , BackendPath + "/bin/remote-dev-server.sh" }
439
+ shellArgs = append (shellArgs , args ... )
440
+ cmd := exec .Command (shell , shellArgs ... )
441
+
442
+ cmd .Env = append (os .Environ (),
443
+ // Set default config and system directories under /workspace to preserve between restarts
444
+ fmt .Sprintf ("IJ_HOST_CONFIG_BASE_DIR=%s" , launchCtx .configDir ),
445
+ fmt .Sprintf ("IJ_HOST_SYSTEM_BASE_DIR=%s" , launchCtx .systemDir ),
446
+ // instead put them into /ide-desktop/backend/bin/idea64.vmoptions
447
+ // otherwise JB will complain to a user on each startup
448
+ // by default remote dev already set -Xmx2048m, see /ide-desktop/backend/plugins/remote-dev-server/bin/launcher.sh
449
+ "JAVA_TOOL_OPTIONS=" ,
454
450
)
455
451
456
452
cmd .Stderr = os .Stderr
@@ -731,3 +727,43 @@ func linkRemotePlugin() error {
731
727
}
732
728
return os .Symlink ("/ide-desktop-plugins/gitpod-remote" , remotePluginDir )
733
729
}
730
+
731
+ // TODO(andreafalzetti): remove dir scanning once this is implemented https://youtrack.jetbrains.com/issue/GTW-2402/Rider-Open-Project-dialog-not-displaying-in-remote-dev
732
+ func findRiderSolutionFile (root string ) (string , error ) {
733
+ slnRegEx := regexp .MustCompile (`^.+\.sln$` )
734
+ projRegEx := regexp .MustCompile (`^.+\.csproj$` )
735
+
736
+ var slnFiles []string
737
+ var csprojFiles []string
738
+
739
+ err := filepath .Walk (root , func (path string , info os.FileInfo , err error ) error {
740
+ if err != nil {
741
+ return err
742
+ } else if slnRegEx .MatchString (info .Name ()) {
743
+ slnFiles = append (slnFiles , path )
744
+ } else if projRegEx .MatchString (info .Name ()) {
745
+ csprojFiles = append (csprojFiles , path )
746
+ }
747
+ return nil
748
+ })
749
+
750
+ if err != nil {
751
+ return "" , err
752
+ }
753
+
754
+ if len (slnFiles ) > 0 {
755
+ return slnFiles [0 ], nil
756
+ } else if len (csprojFiles ) > 0 {
757
+ return csprojFiles [0 ], nil
758
+ }
759
+
760
+ return root , nil
761
+ }
762
+
763
+ func resolveProjectContextDir (launchCtx * LaunchContext ) string {
764
+ if launchCtx .alias == "rider" {
765
+ return launchCtx .riderSolutionFile
766
+ }
767
+
768
+ return launchCtx .projectDir
769
+ }
0 commit comments