Skip to content

Commit d6ddf70

Browse files
committed
[GR-60495] Adds utility for checking import order in espresso.
PullRequest: graal/19575
2 parents 827906d + 9a2c6ef commit d6ddf70

File tree

138 files changed

+608
-371
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

138 files changed

+608
-371
lines changed

espresso/ci/ci_common/common.jsonnet

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ local benchmark_suites = ['dacapo', 'renaissance', 'scala-dacapo'];
277277

278278
local _builds = [
279279
// Gates
280-
that.jdk21_gate_linux_amd64 + that.eclipse + that.jdt + that.predicates(false, false, false) + that.espresso_gate(allow_warnings=false, tags='style,fullbuild', timelimit='35:00', name='gate-espresso-style-jdk21-linux-amd64'),
280+
that.jdk21_gate_linux_amd64 + that.eclipse + that.jdt + that.predicates(false, false, false) + that.espresso_gate(allow_warnings=false, tags='style,fullbuild,imports', timelimit='35:00', name='gate-espresso-style-jdk21-linux-amd64'),
281281
],
282282

283283
builds: utils.add_defined_in(_builds, std.thisFile),

espresso/mx.espresso/import_order.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
#
2+
# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
#
5+
# This code is free software; you can redistribute it and/or modify it
6+
# under the terms of the GNU General Public License version 2 only, as
7+
# published by the Free Software Foundation.
8+
#
9+
# This code is distributed in the hope that it will be useful, but WITHOUT
10+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
# version 2 for more details (a copy is included in the LICENSE file that
13+
# accompanied this code).
14+
#
15+
# You should have received a copy of the GNU General Public License version
16+
# 2 along with this work; if not, write to the Free Software Foundation,
17+
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
#
19+
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
# or visit www.oracle.com if you need additional information or have any
21+
# questions.
22+
#
23+
24+
"""Provides import ordering capabilities for .java files."""
25+
26+
from glob import iglob
27+
28+
# If a given line is an import, it will end with this suffix.
29+
# Used to strip this suffix for faster string comparison.
30+
SUFFIX = ";\n"
31+
32+
STATIC_PREFIX = "import static "
33+
REGULAR_PREFIX = "import "
34+
35+
def verify_order(path, prefix_order):
36+
"""
37+
Verifies import order of java files under the given path.
38+
39+
Iterates over all '.java' files in the given path, recursively over its subfolders.
40+
It then checks that imports in these files are ordered.
41+
42+
Here are the rules:
43+
1. All imports that starts with a suffix that appears in this list must
44+
be found before any other import with a suffix that appears later in
45+
this list.
46+
2. All imports with a given suffix must be in lexicographic order within
47+
all other imports with the same prefix.
48+
3. Static imports must appear before regular imports.
49+
50+
:param path: Where to look for the java files.
51+
:param prefix_order: An ordered list of expected suffixes.
52+
:return: The list of files violating the specified order.
53+
"""
54+
55+
# Validate the prefixes
56+
err = validate_format(prefix_order)
57+
if err:
58+
# Failure is represented with a non-empty list
59+
return [err]
60+
61+
# Start building definitive list of import prefixes
62+
static_prefixes = []
63+
regular_prefixes = []
64+
65+
for prefix in prefix_order:
66+
if prefix:
67+
# If prefix is "abc", add "import abc"
68+
regular_prefixes.append(REGULAR_PREFIX + prefix + '.')
69+
# Eclipse formatting does not enforce prefix order for static imports.
70+
else:
71+
# Empty prefix means everything will match.
72+
# Empty prefix is added manually below.
73+
break
74+
75+
# Ensure we have the empty prefix
76+
# Add "import static "
77+
static_prefixes.append(STATIC_PREFIX)
78+
# Add "import "
79+
regular_prefixes.append(REGULAR_PREFIX)
80+
81+
# Ensures static imports are before regular imports.
82+
prefix_format = static_prefixes + regular_prefixes
83+
84+
invalid_files = []
85+
86+
def is_sorted(li):
87+
if len(li) <= 1:
88+
return True
89+
return all(li[i] <= li[i + 1] for i in range(len(li) - 1))
90+
91+
def check_file(to_check, prefix_format):
92+
imports, prefix_ordered = get_imports(to_check, prefix_format)
93+
94+
if not prefix_ordered:
95+
return False
96+
97+
for import_list in imports:
98+
if not is_sorted(import_list):
99+
return False
100+
101+
return True
102+
103+
for file in iglob(path + '/**/*.java', recursive=True):
104+
if not check_file(file, prefix_format):
105+
invalid_files.append(file)
106+
107+
return invalid_files
108+
109+
def validate_format(prefix_order):
110+
"""
111+
Validates a given ordered list of prefix for import order verification.
112+
113+
Returns the reason for failure of validation if any, or an empty string
114+
if the prefixes are well-formed.
115+
"""
116+
for prefix in prefix_order:
117+
if prefix.endswith('.'):
118+
return "Invalid format for the ordered prefixes: \n'" + prefix + "' must not end with a '.'"
119+
return ""
120+
121+
def get_imports(file, prefix_format):
122+
"""
123+
Obtains list of imports list, each corresponding to each specified prefix.
124+
Also returns whether the found prefixes were ordered.
125+
126+
In case the prefixes where not ordered, the last element of the returned list will contain
127+
every import after the violating line
128+
"""
129+
def add_import(li, value, prefix, suf):
130+
to_add = value[len(prefix):]
131+
if to_add.endswith(suf):
132+
to_add = to_add[:len(to_add) - len(suf)]
133+
li.append(to_add)
134+
135+
def enter_fail_state(imports, prefix_format, cur_prefix_imports):
136+
if cur_prefix_imports:
137+
imports.append(cur_prefix_imports)
138+
return False, len(prefix_format), ""
139+
140+
with open(file) as f:
141+
imports = []
142+
prefix_ordered = True
143+
144+
cur_prefix_idx = 0
145+
cur_prefix = prefix_format[cur_prefix_idx]
146+
147+
cur_prefix_imports = []
148+
149+
for line in f.readlines():
150+
ignore = not line.startswith("import")
151+
if ignore:
152+
# start of class declaration, we can stop looking for imports.
153+
end = 'class ' in line or 'interface ' in line or 'enum ' in line or 'record ' in line
154+
if end:
155+
break
156+
continue
157+
158+
if line.startswith(cur_prefix):
159+
# If we are still ensuring prefix ordering, ensure that this line does not belong
160+
# to a previous prefix.
161+
if prefix_ordered:
162+
for i in range(cur_prefix_idx):
163+
if line.startswith(prefix_format[i]):
164+
# A match for a previous prefix was found: enter fail state
165+
prefix_ordered, cur_prefix_idx, cur_prefix = enter_fail_state(imports, prefix_format, cur_prefix_imports)
166+
cur_prefix_imports = []
167+
add_import(cur_prefix_imports, line, cur_prefix, SUFFIX)
168+
else:
169+
# cur_prefix not found, advance to next prefix if found, report failure if not.
170+
for i in range(cur_prefix_idx + 1, len(prefix_format)):
171+
if line.startswith(prefix_format[i]):
172+
# Report imports for current prefix,
173+
if cur_prefix_imports:
174+
imports.append(cur_prefix_imports)
175+
# Set state to next prefix.
176+
cur_prefix = prefix_format[i]
177+
cur_prefix_idx = i
178+
cur_prefix_imports = []
179+
add_import(cur_prefix_imports, line, cur_prefix, SUFFIX)
180+
break
181+
else:
182+
# On failure, dump remaining lines into the last cur_prefix_imports.
183+
prefix_ordered, cur_prefix_idx, cur_prefix = enter_fail_state(imports, prefix_format, cur_prefix_imports)
184+
cur_prefix_imports = []
185+
add_import(cur_prefix_imports, line, cur_prefix, SUFFIX)
186+
187+
if cur_prefix_imports:
188+
imports.append(cur_prefix_imports)
189+
190+
return imports, prefix_ordered

espresso/mx.espresso/mx_espresso.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,12 @@
4141
from mx_gate import Task, add_gate_runner
4242
from mx_jackpot import jackpot
4343
from os.path import join, isabs, exists, dirname, relpath, basename
44+
from import_order import verify_order, validate_format
4445

4546
_suite = mx.suite('espresso')
4647

4748
# re-export custom mx project classes, so they can be used from suite.py
48-
from mx_sdk_shaded import ShadedLibraryProject # pylint: disable=unused-import
49+
from mx_sdk_shaded import ShadedLibraryProject
4950

5051
# JDK compiled with the Sulong toolchain.
5152
espresso_llvm_java_home = mx.get_env('ESPRESSO_LLVM_JAVA_HOME') or mx.get_env('LLVM_JAVA_HOME')
@@ -134,9 +135,48 @@ def _run_espresso_meta(args, nonZeroIsFatal=True, timeout=None):
134135
] + _espresso_standalone_command(args, allow_jacoco=False), nonZeroIsFatal=nonZeroIsFatal, timeout=timeout)
135136

136137

138+
def _run_verify_imports(s):
139+
# Look for the format specification in the suite
140+
prefs = s.eclipse_settings_sources().get('org.eclipse.jdt.ui.prefs')
141+
prefix_order = []
142+
if prefs:
143+
for pref in prefs:
144+
with open(pref) as f:
145+
for line in f.readlines():
146+
if line.startswith('org.eclipse.jdt.ui.importorder'):
147+
key_value_sep_index = line.find('=')
148+
if key_value_sep_index != -1:
149+
value = line.strip()[key_value_sep_index + 1:]
150+
prefix_order = value.split(';')
151+
152+
# Validate import order format
153+
err = validate_format(prefix_order)
154+
if err:
155+
mx.abort(err)
156+
157+
# Find invalid files
158+
invalid_files = []
159+
for project in s.projects:
160+
if isinstance(project, ShadedLibraryProject):
161+
# Ignore shaded libraries
162+
continue
163+
for src_dir in project.source_dirs():
164+
invalid_files += verify_order(src_dir, prefix_order)
165+
166+
if invalid_files:
167+
mx.abort("The following files have wrong imports order:\n" + '\n'.join(invalid_files))
168+
169+
print("All imports correctly ordered!")
170+
171+
def _run_verify_imports_espresso(args):
172+
if args:
173+
mx.abort("No arguments expected for verify-imports")
174+
_run_verify_imports(_suite)
175+
137176
class EspressoTags:
138177
jackpot = 'jackpot'
139178
verify = 'verify'
179+
imports = 'imports'
140180

141181

142182
def _espresso_gate_runner(args, tasks):
@@ -149,6 +189,10 @@ def _espresso_gate_runner(args, tasks):
149189
if t:
150190
mx_sdk_vm.verify_graalvm_configs(suites=['espresso'])
151191

192+
with Task('Espresso: verify import order', tasks, tags=[EspressoTags.imports]) as t:
193+
if t:
194+
_run_verify_imports(_suite)
195+
152196
mokapot_header_gate_name = 'Verify consistency of mokapot headers'
153197
with Task(mokapot_header_gate_name, tasks, tags=[EspressoTags.verify]) as t:
154198
if t:
@@ -831,6 +875,7 @@ def gen_gc_option_check(args):
831875
'java-truffle': [_run_java_truffle, '[args]'],
832876
'espresso-meta': [_run_espresso_meta, '[args]'],
833877
'gen-gc-option-check': [gen_gc_option_check, '[path to isolate-creation-only-options.txt]'],
878+
'verify-imports': [_run_verify_imports_espresso, ''],
834879
})
835880

836881

espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClassfileStream.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222
*/
2323
package com.oracle.truffle.espresso.classfile;
2424

25-
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
26-
import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence;
27-
2825
import java.nio.BufferUnderflowException;
2926
import java.nio.ByteBuffer;
3027
import java.nio.ByteOrder;
3128

29+
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
30+
import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence;
31+
3232
/**
3333
* Operations for sequentially scanning data items in a class file. Any IO exceptions that occur
3434
* during scanning are converted to {@link ParserException.ClassFormatError}s.

espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClasspathEntry.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
*/
2323
package com.oracle.truffle.espresso.classfile;
2424

25-
import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence;
26-
2725
import java.io.File;
2826

27+
import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence;
28+
2929
/**
3030
* An entry in a classpath is a file system path that denotes an existing directory, an existing
3131
* zip/jar file or a file.

espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ClasspathFile.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
*/
2323
package com.oracle.truffle.espresso.classfile;
2424

25-
import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence;
26-
2725
import java.io.File;
2826

27+
import com.oracle.truffle.espresso.classfile.descriptors.ByteSequence;
28+
2929
/**
3030
* Encapsulates the contents of a file loaded from an {@linkplain ClasspathEntry entry} on a
3131
* classpath.

espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ConstantPool.java

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,23 @@
2222
*/
2323
package com.oracle.truffle.espresso.classfile;
2424

25+
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.CLASS;
26+
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.DOUBLE;
27+
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.FIELD_REF;
28+
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.FLOAT;
29+
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INTEGER;
30+
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INTERFACE_METHOD_REF;
31+
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INVOKEDYNAMIC;
32+
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.LONG;
33+
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.METHOD_REF;
34+
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.NAME_AND_TYPE;
35+
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.STRING;
36+
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.UTF8;
37+
38+
import java.util.Arrays;
39+
import java.util.Formatter;
40+
2541
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
26-
import com.oracle.truffle.espresso.classfile.descriptors.Symbol;
27-
import com.oracle.truffle.espresso.classfile.descriptors.Symbol.ModifiedUTF8;
2842
import com.oracle.truffle.espresso.classfile.constantpool.ClassConstant;
2943
import com.oracle.truffle.espresso.classfile.constantpool.ClassMethodRefConstant;
3044
import com.oracle.truffle.espresso.classfile.constantpool.DoubleConstant;
@@ -40,22 +54,8 @@
4054
import com.oracle.truffle.espresso.classfile.constantpool.PoolConstant;
4155
import com.oracle.truffle.espresso.classfile.constantpool.StringConstant;
4256
import com.oracle.truffle.espresso.classfile.constantpool.Utf8Constant;
43-
44-
import java.util.Arrays;
45-
import java.util.Formatter;
46-
47-
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.CLASS;
48-
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.DOUBLE;
49-
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.FIELD_REF;
50-
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.FLOAT;
51-
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INTEGER;
52-
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INTERFACE_METHOD_REF;
53-
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.INVOKEDYNAMIC;
54-
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.LONG;
55-
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.METHOD_REF;
56-
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.NAME_AND_TYPE;
57-
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.STRING;
58-
import static com.oracle.truffle.espresso.classfile.ConstantPool.Tag.UTF8;
57+
import com.oracle.truffle.espresso.classfile.descriptors.Symbol;
58+
import com.oracle.truffle.espresso.classfile.descriptors.Symbol.ModifiedUTF8;
5959

6060
/**
6161
* Immutable, shareable constant-pool representation.

espresso/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/ImmutableConstantPool.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@
2222
*/
2323
package com.oracle.truffle.espresso.classfile;
2424

25+
import java.nio.ByteBuffer;
26+
import java.util.Arrays;
27+
import java.util.Objects;
28+
2529
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
2630
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
27-
import com.oracle.truffle.espresso.classfile.descriptors.Symbol;
2831
import com.oracle.truffle.espresso.classfile.constantpool.ClassConstant;
2932
import com.oracle.truffle.espresso.classfile.constantpool.PoolConstant;
3033
import com.oracle.truffle.espresso.classfile.constantpool.Utf8Constant;
31-
32-
import java.nio.ByteBuffer;
33-
import java.util.Arrays;
34-
import java.util.Objects;
34+
import com.oracle.truffle.espresso.classfile.descriptors.Symbol;
3535

3636
/**
3737
* Immutable constant pool implementation backed by an array of constants.

0 commit comments

Comments
 (0)