forked from graalvm/mx
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmx_ide_eclipse.py
1336 lines (1157 loc) · 62.5 KB
/
mx_ide_eclipse.py
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#
# ----------------------------------------------------------------------------------------------------
#
# Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#
# ----------------------------------------------------------------------------------------------------
#
import os, time, zipfile, tempfile
# TODO use defusedexpat?
import xml.parsers.expat, xml.sax.saxutils, xml.dom.minidom
from collections import namedtuple
from argparse import ArgumentParser, FileType
from contextlib import ExitStack
from os.path import join, basename, dirname, exists, isdir, abspath
from io import StringIO
import mx
import mx_ideconfig
import mx_javamodules
def eclipseinit_and_format_files(eclipse_exe, config, files):
"""Wrapper for :func:`_format_files` that automatically initializes the workspace and eclipse ini
with a temporary configuration"""
wsroot = eclipseinit([], buildProcessorJars=False, doFsckProjects=False)
with _TempEclipseIni(eclipse_exe) as tmp_eclipseini:
_format_files(eclipse_exe, wsroot, tmp_eclipseini.name, config, files)
def _format_files(eclipse_exe, wsroot, eclipse_ini, config, files):
"""Formats a list of files with the given Eclipse instance
:param eclipse_exe the eclipse executable to use for formatting
:param wsroot an initialized eclipse workspace root
:param eclipse_ini the eclipse ini configuration file to use
:param config the JavaCodeFormatter config to use
:param files a list of file paths to format
"""
capture = mx.OutputCapture()
jdk = mx.get_jdk()
rc = mx.run([eclipse_exe,
'--launcher.ini', eclipse_ini,
'-nosplash',
'-application',
'-consolelog',
'-data', wsroot,
'-vm', jdk.java,
'org.eclipse.jdt.core.JavaCodeFormatter',
'-config', config]
+ files, out=capture, err=capture, nonZeroIsFatal=False)
if rc != 0:
mx.log(capture.data)
mx.abort("Error while running formatter")
class _TempEclipseIni:
"""Context manager that initializes a temporary eclipse ini file for the given eclipse executable.
Upon exit, the temporary configuration file is automatically removed."""
def __init__(self, eclipse_exe):
self.eclipse_exe = eclipse_exe
self.tmp_eclipseini = tempfile.NamedTemporaryFile(mode='w')
def __enter__(self):
# Use an ExitStack to make sure that the NamedTemporaryFile is closed in case
# there is an exception while writing its content
with ExitStack() as stack:
stack.enter_context(self.tmp_eclipseini)
with open(join(dirname(self.eclipse_exe),
join('..', 'Eclipse', 'eclipse.ini') if mx.is_darwin() else 'eclipse.ini'), 'r') as src:
locking_added = False
for line in src.readlines():
self.tmp_eclipseini.write(line)
if line.strip() == '-vmargs':
self.tmp_eclipseini.write('-Dosgi.locking=none\n')
locking_added = True
if not locking_added:
self.tmp_eclipseini.write('-vmargs\n-Dosgi.locking=none\n')
self.tmp_eclipseini.flush()
# Everything went fine, we will close the NamedTemporaryFile in our own exit method
stack.pop_all()
return self.tmp_eclipseini
def __exit__(self, *args):
self.tmp_eclipseini.__exit__(*args)
@mx.command('mx', 'eclipseformat')
def eclipseformat(args):
"""run the Eclipse Code Formatter on the Java sources
The exit code 1 denotes that at least one file was modified."""
parser = ArgumentParser(prog='mx eclipseformat')
parser.add_argument('-e', '--eclipse-exe', help='location of the Eclipse executable')
parser.add_argument('-C', '--no-backup', action='store_false', dest='backup', help='do not save backup of modified files')
parser.add_argument('--projects', action='store', help='comma separated projects to process (omit to process all projects)')
parser.add_argument('--primary', action='store_true', help='limit checks to primary suite')
parser.add_argument('--patchfile', type=FileType("w"), help='file to which a patch denoting the applied formatting changes is written')
parser.add_argument('--restore', action='store_true', help='restore original files after the formatting job (does not create a backup).')
parser.add_argument('--filelist', type=FileType("r"), help='only format the files listed in the given file')
args = parser.parse_args(args)
if args.restore:
args.backup = False
eclipse_exe = locate_eclipse_exe(args.eclipse_exe)
if eclipse_exe is None:
mx.abort('Could not find Eclipse executable. Use -e option or ensure ECLIPSE_EXE environment variable is set.')
filelist = None
if args.filelist:
filelist = [abspath(line.strip()) for line in args.filelist.readlines()]
args.filelist.close()
wsroot = eclipseinit([], buildProcessorJars=False, doFsckProjects=False)
# build list of projects to be processed
if args.projects is not None:
projectsToProcess = [mx.project(name) for name in args.projects.split(',')]
elif args.primary:
projectsToProcess = mx.projects(limit_to_primary=True)
else:
projectsToProcess = mx.projects(opt_limit_to_suite=True)
class Batch:
def __init__(self, settingsDir):
self.path = join(settingsDir, 'org.eclipse.jdt.core.prefs')
with open(join(settingsDir, 'org.eclipse.jdt.ui.prefs')) as fp:
jdtUiPrefs = fp.read()
self.removeTrailingWhitespace = 'sp_cleanup.remove_trailing_whitespaces_all=true' in jdtUiPrefs
if self.removeTrailingWhitespace:
assert 'sp_cleanup.remove_trailing_whitespaces=true' in jdtUiPrefs and 'sp_cleanup.remove_trailing_whitespaces_ignore_empty=false' in jdtUiPrefs
self.cachedHash = None
def __hash__(self):
if not self.cachedHash:
self.cachedHash = (self.read_core_prefs_file(), self.removeTrailingWhitespace).__hash__()
return self.cachedHash
def __eq__(self, other):
if not isinstance(other, Batch):
return False
if self.removeTrailingWhitespace != other.removeTrailingWhitespace:
return False
if self.path == other.path:
return True
return self.read_core_prefs_file() == other.read_core_prefs_file()
def read_core_prefs_file(self):
with open(self.path) as fp:
content = fp.read()
# processAnnotations does not matter for eclipseformat, ignore its value as otherwise we would create extra batches and slow down eclipseformat
content = content.replace('org.eclipse.jdt.core.compiler.processAnnotations=disabled\n', '').replace('org.eclipse.jdt.core.compiler.processAnnotations=enabled\n', '')
return content
modified = list()
batches = dict() # all sources with the same formatting settings are formatted together
for p in projectsToProcess:
if not p.isJavaProject():
continue
sourceDirs = p.source_dirs()
batch = Batch(join(p.dir, '.settings'))
if not exists(batch.path):
if mx._opts.verbose:
mx.log(f'[no Eclipse Code Formatter preferences at {batch.path} - skipping]')
continue
javafiles = []
for sourceDir in sourceDirs:
for root, _, files in os.walk(sourceDir):
for f in [join(root, name) for name in files if name.endswith('.java')]:
if filelist is None or f in filelist:
javafiles.append(mx.FileInfo(f))
if len(javafiles) == 0:
mx.logv(f'[no Java sources in {p.name} - skipping]')
continue
res = batches.setdefault(batch, javafiles)
if res is not javafiles:
res.extend(javafiles)
mx.log("we have: " + str(len(batches)) + " batches")
batch_num = 0
for batch, javafiles in batches.items():
batch_num += 1
mx.log(f"Processing batch {batch_num} ({len(javafiles)} files)...")
with _TempEclipseIni(eclipse_exe) as tmp_eclipseini:
for chunk in mx._chunk_files_for_command_line(javafiles, pathFunction=lambda f: f.path):
_format_files(eclipse_exe, wsroot, tmp_eclipseini.name, batch.path, [f.path for f in chunk])
for fi in chunk:
if fi.update(batch.removeTrailingWhitespace, args.restore):
modified.append(fi)
mx.log(f'{len(modified)} files were modified')
if len(modified) != 0:
arcbase = mx.primary_suite().dir
if args.backup:
backup = os.path.abspath('eclipseformat.backup.zip')
zf = zipfile.ZipFile(backup, 'w', zipfile.ZIP_DEFLATED)
for fi in modified:
diffs = ''.join(fi.diff)
if args.patchfile:
args.patchfile.write(diffs)
name = os.path.relpath(fi.path, arcbase)
mx.log(f' - {name}')
mx.log('Changes:')
mx.log(diffs)
if args.backup:
arcname = name.replace(os.sep, '/')
zf.writestr(arcname, fi.content)
if args.backup:
zf.close()
mx.log(f'Wrote backup of {len(modified)} modified files to {backup}')
if args.patchfile:
mx.log(f'Wrote patches to {args.patchfile.name}')
args.patchfile.close()
return 1
return 0
def locate_eclipse_exe(eclipse_exe):
"""
Tries to locate an Eclipse executable starting with the given path.
If the path is None, checks the ECLIPSE_EXE environment variable.
If the path is a directory, tries to locate the eclipse executable below the directory.
If the path is a file, ensures that the file is executable.
Returns a path to the Eclipse executable if one could be found, None otherwise.
"""
if eclipse_exe is None:
eclipse_exe = os.environ.get('ECLIPSE_EXE')
if eclipse_exe is None:
return None
# Maybe an Eclipse installation dir was specified - look for the executable in it
if isdir(eclipse_exe):
eclipse_exe = join(eclipse_exe, mx.exe_suffix('eclipse'))
mx.warn("The eclipse-exe was a directory, now using " + eclipse_exe)
if not os.path.isfile(eclipse_exe):
mx.abort('File does not exist: ' + eclipse_exe)
if not os.access(eclipse_exe, os.X_OK):
mx.abort('Not an executable file: ' + eclipse_exe)
return eclipse_exe
def _source_locator_memento(deps, jdk=None):
slm = mx.XMLDoc()
slm.open('sourceLookupDirector')
slm.open('sourceContainers', {'duplicates' : 'false'})
javaCompliance = None
sources = []
for dep in deps:
if dep.isLibrary():
if hasattr(dep, 'eclipse.container'):
memento = mx.XMLDoc().element('classpathContainer', {'path' : getattr(dep, 'eclipse.container')}).xml(standalone='no')
slm.element('classpathContainer', {'memento' : memento, 'typeId':'org.eclipse.jdt.launching.sourceContainer.classpathContainer'})
sources.append(getattr(dep, 'eclipse.container') +' [classpathContainer]')
elif dep.get_source_path(resolve=True):
memento = mx.XMLDoc().element('archive', {'detectRoot' : 'true', 'path' : dep.get_source_path(resolve=True)}).xml(standalone='no')
slm.element('container', {'memento' : memento, 'typeId':'org.eclipse.debug.core.containerType.externalArchive'})
sources.append(dep.get_source_path(resolve=True) + ' [externalArchive]')
elif dep.isJdkLibrary():
if jdk is None:
jdk = mx.get_jdk(tag='default')
path = dep.get_source_path(jdk)
if path:
if os.path.isdir(path):
memento = mx.XMLDoc().element('directory', {'nest' : 'false', 'path' : path}).xml(standalone='no')
slm.element('container', {'memento' : memento, 'typeId':'org.eclipse.debug.core.containerType.directory'})
sources.append(path + ' [directory]')
else:
memento = mx.XMLDoc().element('archive', {'detectRoot' : 'true', 'path' : path}).xml(standalone='no')
slm.element('container', {'memento' : memento, 'typeId':'org.eclipse.debug.core.containerType.externalArchive'})
sources.append(path + ' [externalArchive]')
elif dep.isProject():
if not dep.isJavaProject():
continue
memento = mx.XMLDoc().element('javaProject', {'name' : dep.name}).xml(standalone='no')
slm.element('container', {'memento' : memento, 'typeId':'org.eclipse.jdt.launching.sourceContainer.javaProject'})
sources.append(dep.name + ' [javaProject]')
if javaCompliance is None or dep.javaCompliance > javaCompliance:
javaCompliance = dep.javaCompliance
if javaCompliance:
jdkContainer = 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/' + _to_EclipseJRESystemLibrary(javaCompliance)
memento = mx.XMLDoc().element('classpathContainer', {'path' : jdkContainer}).xml(standalone='no')
slm.element('classpathContainer', {'memento' : memento, 'typeId':'org.eclipse.jdt.launching.sourceContainer.classpathContainer'})
sources.append(jdkContainer + ' [classpathContainer]')
else:
memento = mx.XMLDoc().element('classpathContainer', {'path' : 'org.eclipse.jdt.launching.JRE_CONTAINER'}).xml(standalone='no')
slm.element('classpathContainer', {'memento' : memento, 'typeId':'org.eclipse.jdt.launching.sourceContainer.classpathContainer'})
sources.append('org.eclipse.jdt.launching.JRE_CONTAINER [classpathContainer]')
slm.close('sourceContainers')
slm.close('sourceLookupDirector')
return slm, sources
### ~~~~~~~~~~~~~ IDE / Eclipse / Netbeans / IntelliJ
def make_eclipse_attach(suite, hostname, port, name=None, deps=None, jdk=None):
"""
Creates an Eclipse launch configuration file for attaching to a Java process.
"""
if deps is None:
deps = []
javaProjects = [p for p in suite.projects if p.isJavaProject()]
if len(javaProjects) == 0:
return None, None
slm, sources = _source_locator_memento(deps, jdk=jdk)
# Without an entry for the "Project:" field in an attach configuration, Eclipse Neon has problems connecting
# to a waiting VM and leaves it hanging. Putting any valid project entry in the field seems to solve it.
firstProjectName = javaProjects[0].name
launch = mx.XMLDoc()
launch.open('launchConfiguration', {'type' : 'org.eclipse.jdt.launching.remoteJavaApplication'})
launch.element('stringAttribute', {'key' : 'org.eclipse.debug.core.source_locator_id', 'value' : 'org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector'})
launch.element('stringAttribute', {'key' : 'org.eclipse.debug.core.source_locator_memento', 'value' : '%s'})
launch.element('booleanAttribute', {'key' : 'org.eclipse.jdt.launching.ALLOW_TERMINATE', 'value' : 'true'})
launch.open('mapAttribute', {'key' : 'org.eclipse.jdt.launching.CONNECT_MAP'})
launch.element('mapEntry', {'key' : 'hostname', 'value' : hostname})
launch.element('mapEntry', {'key' : 'port', 'value' : port})
launch.close('mapAttribute')
launch.element('stringAttribute', {'key' : 'org.eclipse.jdt.launching.PROJECT_ATTR', 'value' : firstProjectName})
launch.element('stringAttribute', {'key' : 'org.eclipse.jdt.launching.VM_CONNECTOR_ID', 'value' : 'org.eclipse.jdt.launching.socketAttachConnector'})
launch.close('launchConfiguration')
launch = launch.xml(newl='\n', standalone='no') % slm.xml(escape=True, standalone='no')
if name is None:
if len(mx.suites()) == 1:
suitePrefix = ''
else:
suitePrefix = suite.name + '-'
name = suitePrefix + 'attach-' + hostname + '-' + port
eclipseLaunches = mx.ensure_dir_exists(join(suite.mxDir, 'eclipse-launches'))
launchFile = join(eclipseLaunches, name + '.launch')
sourcesFile = join(eclipseLaunches, name + '.sources')
mx.update_file(sourcesFile, '\n'.join(sources))
return mx.update_file(launchFile, launch), launchFile
def make_eclipse_launch(suite, javaArgs, jre, name=None, deps=None):
"""
Creates an Eclipse launch configuration file for running/debugging a Java command.
"""
if deps is None:
deps = []
mainClass = None
vmArgs = []
appArgs = []
cp = None
argsCopy = list(reversed(javaArgs))
while len(argsCopy) != 0:
a = argsCopy.pop()
if a == '-jar':
mainClass = '-jar'
appArgs = list(reversed(argsCopy))
break
if a in mx._VM_OPTS_SPACE_SEPARATED_ARG:
assert len(argsCopy) != 0
cp = argsCopy.pop()
vmArgs.append(a)
vmArgs.append(cp)
elif a.startswith('-'):
vmArgs.append(a)
else:
mainClass = a
appArgs = list(reversed(argsCopy))
break
if mainClass is None:
mx.log('Cannot create Eclipse launch configuration without main class or jar file: java ' + ' '.join(javaArgs))
return False
if name is None:
if mainClass == '-jar':
name = basename(appArgs[0])
if len(appArgs) > 1 and not appArgs[1].startswith('-'):
name = name + '_' + appArgs[1]
else:
name = mainClass
name = time.strftime('%Y-%m-%d-%H%M%S_' + name)
if cp is not None:
for e in cp.split(os.pathsep):
for s in mx.suites():
deps += [p for p in s.projects if e == p.output_dir()]
deps += [l for l in s.libs if e == l.get_path(False)]
slm, sources = _source_locator_memento(deps)
launch = mx.XMLDoc()
launch.open('launchConfiguration', {'type' : 'org.eclipse.jdt.launching.localJavaApplication'})
launch.element('stringAttribute', {'key' : 'org.eclipse.debug.core.source_locator_id', 'value' : 'org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector'})
launch.element('stringAttribute', {'key' : 'org.eclipse.debug.core.source_locator_memento', 'value' : '%s'})
launch.element('stringAttribute', {'key' : 'org.eclipse.jdt.launching.JRE_CONTAINER', 'value' : 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/' + jre})
launch.element('stringAttribute', {'key' : 'org.eclipse.jdt.launching.MAIN_TYPE', 'value' : mainClass})
launch.element('stringAttribute', {'key' : 'org.eclipse.jdt.launching.PROGRAM_ARGUMENTS', 'value' : ' '.join(appArgs)})
launch.element('stringAttribute', {'key' : 'org.eclipse.jdt.launching.PROJECT_ATTR', 'value' : ''})
launch.element('stringAttribute', {'key' : 'org.eclipse.jdt.launching.VM_ARGUMENTS', 'value' : ' '.join(vmArgs)})
launch.close('launchConfiguration')
launch = launch.xml(newl='\n', standalone='no') % slm.xml(escape=True, standalone='no')
eclipseLaunches = mx.ensure_dir_exists(join(suite.mxDir, 'eclipse-launches'))
launchFile = join(eclipseLaunches, name + '.launch')
sourcesFile = join(eclipseLaunches, name + '.sources')
mx.update_file(sourcesFile, '\n'.join(sources))
return mx.update_file(launchFile, launch)
@mx.command('mx', 'eclipseinit')
def eclipseinit_cli(args):
"""(re)generate Eclipse project configurations and working sets"""
parser = ArgumentParser(prog='mx eclipseinit')
parser.add_argument('--no-build', action='store_false', dest='buildProcessorJars', help='Do not build annotation processor jars.')
parser.add_argument('--no-python-projects', action='store_false', dest='pythonProjects', help='Do not generate PyDev projects for the mx python projects.')
parser.add_argument('-C', '--log-to-console', action='store_true', dest='logToConsole', help='Send builder output to eclipse console.')
parser.add_argument('-f', '--force', action='store_true', dest='force', default=False, help='Ignore timestamps when updating files.')
parser.add_argument('-A', '--absolute-paths', action='store_true', dest='absolutePaths', default=False, help='Use absolute paths in project files.')
args = parser.parse_args(args)
eclipseinit(None, args.buildProcessorJars, logToConsole=args.logToConsole, force=args.force, absolutePaths=args.absolutePaths, pythonProjects=args.pythonProjects)
mx.log('----------------------------------------------')
workspace_dir = os.path.dirname(os.path.abspath(mx.primary_suite().vc_dir))
mx.log('Eclipse project generation successfully completed for:')
mx.log(' ' + (os.linesep + " ").join(sorted([suite.dir for suite in mx.suites(True)])))
mx.log('')
mx.log('The recommended next steps are:')
mx.log(f' 1) Open Eclipse with workspace path: {workspace_dir}')
mx.log(' 2) Open project import wizard using: File -> Import -> Existing Projects into Workspace -> Next.')
mx.log(f' 3) For "select root directory" enter path {workspace_dir}')
mx.log(' 4) Make sure "Search for nested projects" is checked and press "Finish".')
mx.log('')
mx.log(' hint) If you select "Close newly imported projects upon completion" then the import is more efficient. ')
mx.log(' Projects needed for development can be opened conveniently using the generated Suite working sets from the context menu.')
mx.log(' 5) Update the type filters (Preferences -> Java -> Appearance -> Type Filters) so that `jdk.*` and `org.graalvm.*` are not filtered.')
mx.log(' Without this, code completion will not work for JVMCI and Graal code.')
mx.log('')
mx.log('Note that setting MX_BUILD_EXPLODED=true can improve Eclipse build times. See "Exploded builds" in the mx README.md.')
mx.log('----------------------------------------------')
if _EclipseJRESystemLibraries:
executionEnvironments = [n for n in _EclipseJRESystemLibraries if n.startswith('JavaSE-')]
installedJREs = [n for n in _EclipseJRESystemLibraries if not n.startswith('JavaSE-')]
if executionEnvironments:
mx.log('Ensure that these Execution Environments have a Compatible JRE in Eclipse (Preferences -> Java -> Installed JREs -> Execution Environments):')
for name in executionEnvironments:
mx.log(' ' + name)
if installedJREs:
mx.log('Ensure that there are Installed JREs with these exact names in Eclipse (Preferences -> Java -> Installed JREs):')
for name in installedJREs:
mx.log(' ' + name)
mx.log('You can set the "JRE name" field for a JDK when initially adding it or later with the "Edit..." button.')
mx.log('See https://help.eclipse.org/photon/topic/org.eclipse.jdt.doc.user/tasks/task-add_new_jre.htm on how to add')
mx.log('a new JDK to Eclipse. Be sure to select "Standard VM" (even on macOS) for the JRE type.')
mx.log('----------------------------------------------')
def eclipseinit(args, buildProcessorJars=True, refreshOnly=False, logToConsole=False, doFsckProjects=True, force=False, absolutePaths=False, pythonProjects=False):
"""(re)generate Eclipse project configurations and working sets"""
for s in mx.suites(True) + [mx._mx_suite]:
_eclipseinit_suite(s, buildProcessorJars, refreshOnly, logToConsole, force, absolutePaths, pythonProjects)
wsroot = generate_eclipse_workingsets()
if doFsckProjects and not refreshOnly:
mx_ideconfig.fsckprojects([])
return wsroot
EclipseLinkedResource = namedtuple('LinkedResource', ['name', 'type', 'location'])
def _eclipse_linked_resource(name, res_type, location):
return EclipseLinkedResource(name, str(res_type), location)
def get_eclipse_project_rel_locationURI(path, eclipseProjectDir):
"""
Gets the URI for a resource relative to an Eclipse project directory (i.e.,
the directory containing the `.project` file for the project). The URI
returned is based on the builtin PROJECT_LOC Eclipse variable.
See http://stackoverflow.com/a/7585095
"""
relpath = os.path.relpath(path, eclipseProjectDir)
names = relpath.split(os.sep)
parents = len([n for n in names if n == '..'])
sep = '/' # Yes, even on Windows...
if parents:
projectLoc = f'PARENT-{parents}-PROJECT_LOC'
else:
projectLoc = 'PROJECT_LOC'
return sep.join([projectLoc] + [n for n in names if n != '..'])
def _get_eclipse_output_path(project_loc, p, linkedResources=None):
"""
Gets the Eclipse path attribute value for the output of project `p` whose
Eclipse configuration is in the directory `project_loc`.
"""
outputDirRel = os.path.relpath(p.output_dir(), project_loc)
if outputDirRel.startswith('..'):
name = basename(outputDirRel)
if linkedResources is not None:
linkedResources.append(_eclipse_linked_resource(name, IRESOURCE_FOLDER, p.output_dir()))
return name
else:
return outputDirRel
#: Highest Execution Environment defined by most recent Eclipse release.
#: https://wiki.eclipse.org/Execution_Environments
#: https://git.eclipse.org/c/jdt/eclipse.jdt.debug.git/plain/org.eclipse.jdt.launching/plugin.properties
_max_Eclipse_JavaExecutionEnvironment = 18 # pylint: disable=invalid-name
_EclipseJRESystemLibraries = set()
def _to_EclipseJRESystemLibrary(compliance):
"""
Converts a Java compliance value to a JRE System Library that
can be put on a project's Build Path.
"""
if not isinstance(compliance, mx.JavaCompliance):
compliance = mx.JavaCompliance(compliance)
if compliance.value > _max_Eclipse_JavaExecutionEnvironment:
res = 'jdk-' + str(compliance)
else:
res = 'JavaSE-' + str(compliance)
_EclipseJRESystemLibraries.add(res)
return res
RelevantResource = namedtuple('RelevantResource', ['path', 'type'])
# http://grepcode.com/file/repository.grepcode.com/java/eclipse.org/4.4.2/org.eclipse.core/resources/3.9.1/org/eclipse/core/resources/IResource.java#76
IRESOURCE_FILE = 1
IRESOURCE_FOLDER = 2
def _add_eclipse_linked_resources(xml_doc, project_loc, linked_resources, absolutePaths=False):
"""
Adds a ``linkedResources`` element to `xml_doc` for the resources described by `linked_resources`.
:param project_loc: directory containing ``.project`` file containing the content of `xml_doc`
"""
if linked_resources:
xml_doc.open('linkedResources')
for lr in linked_resources:
xml_doc.open('link')
xml_doc.element('name', data=lr.name)
xml_doc.element('type', data=lr.type)
xml_doc.element('locationURI', data=get_eclipse_project_rel_locationURI(lr.location, project_loc) if not absolutePaths else lr.location)
xml_doc.close('link')
xml_doc.close('linkedResources')
def _eclipse_project_rel(project_loc, path, linked_resources, res_type=IRESOURCE_FOLDER):
"""
Converts `path` to be relative to `project_loc`, adding a linked
resource to `linked_resources` if `path` is not under `project_loc`.
:param str res_type: IRESOURCE_FOLDER if path denotes a directory, IRESOURCE_FILE for a regular file
"""
if not path.startswith(project_loc):
name = basename(path)
linked_resources.append(_eclipse_linked_resource(name, res_type, path))
return name
else:
return os.path.relpath(path, project_loc)
def _eclipseinit_project(p, files=None, libFiles=None, absolutePaths=False):
# PROJECT_LOC Eclipse variable
project_loc = mx.ensure_dir_exists(p.dir)
linkedResources = []
out = mx.XMLDoc()
out.open('classpath')
def _add_src_classpathentry(path, attributes=None):
out.open('classpathentry', {'kind' : 'src', 'path' : _eclipse_project_rel(project_loc, path, linkedResources)})
if attributes:
out.open('attributes')
for name, value in attributes.items():
out.element('attribute', {'name' : name, 'value' : value})
out.close('attributes')
out.close('classpathentry')
for src in p.srcDirs:
_add_src_classpathentry(mx.ensure_dir_exists(join(p.dir, src)))
processors = p.annotation_processors()
if processors:
gen_dir = mx.ensure_dir_exists(p.source_gen_dir())
# ignore warnings produced by third-party annotation processors
has_external_processors = any((ap for ap in p.declaredAnnotationProcessors if ap.isLibrary()))
attributes = {'ignore_optional_problems': 'true'} if has_external_processors else None
_add_src_classpathentry(gen_dir, attributes)
if files:
files.append(gen_dir)
if exists(join(p.dir, 'plugin.xml')): # eclipse plugin project
out.element('classpathentry', {'kind' : 'con', 'path' : 'org.eclipse.pde.core.requiredPlugins'})
projectDeps = []
jdk = mx.get_jdk(p.javaCompliance)
def preVisitDep(dep, edge):
if dep.isLibrary() and hasattr(dep, 'eclipse.container'):
container = getattr(dep, 'eclipse.container')
out.element('classpathentry', {'exported' : 'true', 'kind' : 'con', 'path' : container})
# Ignore the dependencies of this library
return False
return True
def processLibraryDep(dep):
assert not hasattr(dep, 'eclipse.container'), dep.name + ' should have been handled in preVisitDep'
path = dep.get_path(resolve=True)
# Relative paths for "lib" class path entries have various semantics depending on the Eclipse
# version being used (e.g. see https://bugs.eclipse.org/bugs/show_bug.cgi?id=274737) so it's
# safest to simply use absolute paths.
# It's important to use dep.suite as the location for when one suite references
# a library in another suite.
path = mx._make_absolute(path, dep.suite.dir)
attributes = {'exported' : 'true', 'kind' : 'lib', 'path' : path}
sourcePath = dep.get_source_path(resolve=True)
if sourcePath is not None:
attributes['sourcepath'] = sourcePath
out.element('classpathentry', attributes)
if libFiles:
libFiles.append(path)
def processJdkLibraryDep(dep):
path = dep.classpath_repr(jdk, resolve=True)
if path:
attributes = {'exported' : 'true', 'kind' : 'lib', 'path' : path}
sourcePath = dep.get_source_path(jdk)
if sourcePath is not None:
attributes['sourcepath'] = sourcePath
out.element('classpathentry', attributes)
if libFiles:
libFiles.append(path)
def processDep(dep, edge):
if dep is p:
return
if dep.isLibrary() or dep.isMavenProject():
processLibraryDep(dep)
elif dep.isJavaProject():
high_bound = dep.javaCompliance._high_bound()
if not high_bound or high_bound >= p.javaCompliance.value:
projectDeps.append(dep)
else:
# Ignore a dep whose highest Java level is less than p's level
pass
elif dep.isNativeProject():
projectDeps.append(dep)
elif dep.isJdkLibrary():
processJdkLibraryDep(dep)
elif dep.isJARDistribution() and isinstance(dep.suite, mx.BinarySuite):
out.element('classpathentry', {'exported' : 'true', 'kind' : 'lib', 'path' : dep.path, 'sourcepath' : dep.sourcesPath})
elif dep.isJreLibrary() or dep.isDistribution():
pass
elif dep.isProject():
mx.logv('ignoring project ' + dep.name + ' for eclipseinit')
else:
mx.abort('unexpected dependency: ' + str(dep))
p.walk_deps(preVisit=preVisitDep, visit=processDep)
# When targeting JDK 8 or earlier, dependencies need to precede the JDK on the Eclipse build path.
# There may be classes in dependencies that are also in the JDK. We want to compile against the
# former. This is the same -Xbootclasspath:/p trick done in JavacLikeCompiler.prepare.
putJREFirstOnBuildPath = p.javaCompliance.value >= 9
allProjectPackages = set()
for dep in projectDeps:
if not dep.isNativeProject():
allProjectPackages.update(dep.defined_java_packages())
if not putJREFirstOnBuildPath:
out.element('classpathentry', {'combineaccessrules' : 'false', 'exported' : 'true', 'kind' : 'src', 'path' : '/' + dep.name})
# Every Java program depends on a JRE
jreSystemLibrary = _to_EclipseJRESystemLibrary(jdk.javaCompliance)
out.open('classpathentry', {'kind' : 'con', 'path' : 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/' + jreSystemLibrary})
if jdk.javaCompliance >= '9':
out.open('attributes')
out.element('attribute', {'name' : 'module', 'value' : 'true'})
moduleDeps = p.get_concealed_imported_packages(jdk=jdk)
if len(moduleDeps) != 0:
# Ignore modules (such as jdk.internal.vm.compiler) that define packages
# that are also defined by project deps as the latter will have the most
# recent API.
exports = sorted([(module, pkgs) for module, pkgs in moduleDeps.items() if allProjectPackages.isdisjoint(pkgs)])
if exports:
addExportsValue = []
exported_modules = []
for module, pkgs in exports:
addExportsValue.extend([module + '/' + pkg + '=ALL-UNNAMED' for pkg in pkgs])
exported_modules.append(module)
out.element('attribute', {'name' : 'add-exports', 'value' : ':'.join(addExportsValue)})
roots = jdk.get_root_modules()
observable_modules = jdk.get_modules()
default_module_graph = mx_javamodules.get_transitive_closure(roots, observable_modules)
module_graph = mx_javamodules.get_transitive_closure(roots + exported_modules, observable_modules)
if default_module_graph != module_graph:
# https://github.com/eclipse/eclipse.jdt.core/blob/00dd337bcfe08d8b2d60529b0f7874b88e621c06/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaProject.java#L704-L715
out.element('attribute', {'name' : 'limit-modules', 'value' : ','.join([m.name for m in module_graph])})
out.close('attributes')
out.close('classpathentry')
if putJREFirstOnBuildPath:
for dep in projectDeps:
if not dep.isNativeProject():
out.element('classpathentry', {'combineaccessrules' : 'false', 'exported' : 'true', 'kind' : 'src', 'path' : '/' + dep.name})
out.element('classpathentry', {'kind' : 'output', 'path' : _get_eclipse_output_path(project_loc, p, linkedResources)})
out.close('classpath')
classpathFile = join(project_loc, '.classpath')
mx.update_file(classpathFile, out.xml(indent='\t', newl='\n'))
if files:
files.append(classpathFile)
csConfig, _, cs_project = p.get_checkstyle_config()
if csConfig:
out = mx.XMLDoc()
dotCheckstyle = join(project_loc, ".checkstyle")
cs_path = _eclipse_project_rel(project_loc, csConfig, linkedResources, IRESOURCE_FILE)
out.open('fileset-config', {'file-format-version' : '1.2.0', 'simple-config' : 'false'})
out.open('local-check-config', {'name' : 'Checks', 'location' : '/' + cs_project.name + '/' + cs_path, 'type' : 'project', 'description' : ''})
out.element('additional-data', {'name' : 'protect-config-file', 'value' : 'false'})
out.close('local-check-config')
out.open('fileset', {'name' : 'all', 'enabled' : 'true', 'check-config-name' : 'Checks', 'local' : 'true'})
out.element('file-match-pattern', {'match-pattern' : r'.*\.java$', 'include-pattern' : 'true'})
out.element('file-match-pattern', {'match-pattern' : p.source_gen_dir_name() + os.sep + '.*', 'include-pattern' : 'false'})
out.element('file-match-pattern', {'match-pattern' : '/package-info.java$', 'include-pattern' : 'false'})
out.close('fileset')
exclude = join(p.dir, '.checkstyle.exclude')
if False and exists(exclude):
out.open('filter', {'name' : 'FilesFromPackage', 'enabled' : 'true'})
with open(exclude) as f:
for line in f:
if not line.startswith('#'):
line = line.strip()
out.element('filter-data', {'value' : line})
out.close('filter')
out.close('fileset-config')
mx.update_file(dotCheckstyle, out.xml(indent=' ', newl='\n'))
if files:
files.append(dotCheckstyle)
else:
# clean up existing .checkstyle file
dotCheckstyle = join(project_loc, ".checkstyle")
if exists(dotCheckstyle):
os.unlink(dotCheckstyle)
out = mx.XMLDoc()
out.open('projectDescription')
out.element('name', data=p.name)
out.element('comment', data='')
out.open('projects')
for dep in projectDeps:
if not dep.isNativeProject():
out.element('project', data=dep.name)
out.close('projects')
out.open('buildSpec')
out.open('buildCommand')
out.element('name', data='org.eclipse.jdt.core.javabuilder')
out.element('arguments', data='')
out.close('buildCommand')
if csConfig:
out.open('buildCommand')
out.element('name', data='net.sf.eclipsecs.core.CheckstyleBuilder')
out.element('arguments', data='')
out.close('buildCommand')
if exists(join(p.dir, 'plugin.xml')): # eclipse plugin project
for buildCommand in ['org.eclipse.pde.ManifestBuilder', 'org.eclipse.pde.SchemaBuilder']:
out.open('buildCommand')
out.element('name', data=buildCommand)
out.element('arguments', data='')
out.close('buildCommand')
out.close('buildSpec')
out.open('natures')
out.element('nature', data='org.eclipse.jdt.core.javanature')
if exists(join(p.dir, 'plugin.xml')): # eclipse plugin project
out.element('nature', data='org.eclipse.pde.PluginNature')
out.close('natures')
_add_eclipse_linked_resources(out, project_loc, linkedResources, absolutePaths)
out.close('projectDescription')
projectFile = join(project_loc, '.project')
mx.update_file(projectFile, out.xml(indent='\t', newl='\n'))
if files:
files.append(projectFile)
# copy a possibly modified file to the project's .settings directory
_copy_eclipse_settings(project_loc, p, files)
if processors:
out = mx.XMLDoc()
out.open('factorypath')
out.element('factorypathentry', {'kind' : 'PLUGIN', 'id' : 'org.eclipse.jst.ws.annotations.core', 'enabled' : 'true', 'runInBatchMode' : 'false'})
processorsPath = mx.classpath_entries(names=processors)
for e in processorsPath:
if e.isDistribution() and not isinstance(e.suite, mx.BinarySuite):
out.element('factorypathentry', {'kind' : 'WKSPJAR', 'id' : f'/{e.name}/{basename(e.path)}', 'enabled' : 'true', 'runInBatchMode' : 'false'})
elif e.isJdkLibrary() or e.isJreLibrary():
path = e.classpath_repr(jdk, resolve=True)
if path:
out.element('factorypathentry', {'kind' : 'EXTJAR', 'id' : path, 'enabled' : 'true', 'runInBatchMode' : 'false'})
else:
out.element('factorypathentry', {'kind' : 'EXTJAR', 'id' : e.classpath_repr(resolve=True), 'enabled' : 'true', 'runInBatchMode' : 'false'})
if p.javaCompliance >= '9':
concealedAPDeps = {}
for dep in mx.classpath_entries(names=processors, preferProjects=True):
if dep.isJavaProject():
concealed = dep.get_concealed_imported_packages(jdk)
if concealed:
for module, pkgs in concealed.items():
concealedAPDeps.setdefault(module, []).extend(pkgs)
if concealedAPDeps:
exports = []
for module, pkgs in concealedAPDeps.items():
for pkg in pkgs:
exports.append('--add-exports=' + module + '/' + pkg + '=ALL-UNNAMED')
mx.warn('Annotation processor(s) for ' + p.name + ' uses non-exported module packages, requiring ' +
'the following to be added to eclipse.ini:\n' +
'\n'.join(exports))
out.close('factorypath')
mx.update_file(join(project_loc, '.factorypath'), out.xml(indent='\t', newl='\n'))
if files:
files.append(join(project_loc, '.factorypath'))
def _capture_eclipse_settings(logToConsole, absolutePaths):
# Capture interesting settings which drive the output of the projects.
# Changes to these values should cause regeneration of the project files.
settings = f'logToConsole={logToConsole}\n'
settings = settings + f'absolutePaths={absolutePaths}\n'
for name, value in mx_ideconfig._get_ide_envvars().items():
settings = settings + f'{name}={value}\n'
return settings
def _eclipseinit_suite(s, buildProcessorJars=True, refreshOnly=False, logToConsole=False, force=False, absolutePaths=False, pythonProjects=False):
# a binary suite archive is immutable and no project sources, only the -sources.jar
# TODO We may need the project (for source debugging) but it needs different treatment
if isinstance(s, mx.BinarySuite):
return
suite_config_dir = mx.ensure_dir_exists(s.get_mx_output_dir())
configZip = mx.TimeStampFile(join(suite_config_dir, 'eclipse-config.zip'))
configLibsZip = join(suite_config_dir, 'eclipse-config-libs.zip')
if refreshOnly and not configZip.exists():
return
settingsFile = join(suite_config_dir, 'eclipse-project-settings')
mx.update_file(settingsFile, _capture_eclipse_settings(logToConsole, absolutePaths))
if not force and mx_ideconfig._check_ide_timestamp(s, configZip, 'eclipse', settingsFile):
mx.logv(f'[Eclipse configurations for {s.name} are up to date - skipping]')
return
files = []
libFiles = []
if buildProcessorJars:
files += mx._processorjars_suite(s)
for p in s.projects:
code = mx._function_code(p._eclipseinit)
if 'absolutePaths' in code.co_varnames[:code.co_argcount]:
p._eclipseinit(files, libFiles, absolutePaths=absolutePaths)
else:
# Support legacy signature
p._eclipseinit(files, libFiles)
jdk = mx.get_jdk(tag='default')
_, launchFile = make_eclipse_attach(s, 'localhost', '8000', deps=mx.dependencies(), jdk=jdk)
if launchFile:
files.append(launchFile)
# Create an Eclipse project for each distribution that will create/update the archive
# for the distribution whenever any (transitively) dependent project of the
# distribution is updated.
for dist in s.dists:
if not dist.isJARDistribution():
continue
project_loc = dist.get_ide_project_dir()
if not project_loc:
continue
mx.ensure_dir_exists(project_loc)
relevantResources = []
relevantResourceDeps = set(dist.archived_deps())
for d in sorted(relevantResourceDeps):
# Eclipse does not seem to trigger a build for a distribution if the references
# to the constituent projects are of type IRESOURCE_PROJECT.
if d.isJavaProject():
for srcDir in d.srcDirs:
relevantResources.append(RelevantResource('/' + d.name + '/' + srcDir, IRESOURCE_FOLDER))
relevantResources.append(RelevantResource('/' + d.name + '/' + _get_eclipse_output_path(project_loc, d), IRESOURCE_FOLDER))
# make sure there is at least one entry otherwise all resources will be implicitly relevant
if not relevantResources:
relevantResources.append(RelevantResource(get_eclipse_project_rel_locationURI(dist.path, project_loc), IRESOURCE_FOLDER))
use_async_distributions = mx.env_var_to_bool('MX_IDE_ECLIPSE_ASYNC_DISTRIBUTIONS')
# if a distribution is used as annotation processor we need to refresh the project
# in order to make eclipse reload the annotation processor jar on changes.
out = mx.XMLDoc()
out.open('projectDescription')
out.element('name', data=dist.name)
out.element('comment', data='Updates ' + dist.path + ' if a project dependency of ' + dist.name + ' is updated')
out.open('projects')
for d in sorted(relevantResourceDeps):
out.element('project', data=d.name)
out.close('projects')
out.open('buildSpec')
dist.dir = project_loc
builders = _genEclipseBuilder(project_loc, out, dist, 'Create' + dist.name + 'Dist', '-v archive @' + dist.name,
relevantResources=relevantResources,
logToFile=True, refresh=True, isAsync=use_async_distributions,
logToConsole=logToConsole, appendToLogFile=False,
refreshFile=f'/{dist.name}/{basename(dist.path)}')
files = files + builders
out.close('buildSpec')
out.open('natures')
out.element('nature', data='org.eclipse.jdt.core.javanature')
out.close('natures')
if dist.definedAnnotationProcessors:
linked_resources = [_eclipse_linked_resource(basename(dist.path), str(IRESOURCE_FILE), dist.path)]
_add_eclipse_linked_resources(out, project_loc, linked_resources, absolutePaths=absolutePaths)
out.close('projectDescription')
projectFile = join(project_loc, '.project')
mx.update_file(projectFile, out.xml(indent='\t', newl='\n'))
files.append(projectFile)
if pythonProjects:
project_loc = s.dir if s is mx._mx_suite else s.mxDir
linked_resources = []
source_path = _eclipse_project_rel(project_loc, s.name if s is mx._mx_suite else s.mxDir, linked_resources)
projectXml = mx.XMLDoc()
projectXml.open('projectDescription')
projectXml.element('name', data=s.name if s is mx._mx_suite else 'mx.' + s.name)
projectXml.element('comment')
projectXml.open('projects')
if s is not mx._mx_suite:
projectXml.element('project', data=mx._mx_suite.name)
processed_suites = set([s.name])
def _mx_projects_suite(visited_suite, suite_import):
if suite_import.name in processed_suites:
return
processed_suites.add(suite_import.name)
dep_suite = mx.suite(suite_import.name)
projectXml.element('project', data='mx.' + suite_import.name)
dep_suite.visit_imports(_mx_projects_suite)
s.visit_imports(_mx_projects_suite)
projectXml.close('projects')
projectXml.open('buildSpec')
projectXml.open('buildCommand')
projectXml.element('name', data='org.python.pydev.PyDevBuilder')
projectXml.element('arguments')
projectXml.close('buildCommand')
projectXml.close('buildSpec')
projectXml.open('natures')