re base code
[sdc.git] / openecomp-be / tools / compile-helper-plugin / src / main / java / org / openecomp / sdc / onboarding / PreCompileHelperMojo.java
1 /*
2  * Copyright © 2018 European Support Limited
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on a "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package org.openecomp.sdc.onboarding;
18
19 import static org.openecomp.sdc.onboarding.BuildHelper.getArtifactPathInLocalRepo;
20 import static org.openecomp.sdc.onboarding.BuildHelper.getChecksum;
21 import static org.openecomp.sdc.onboarding.BuildHelper.getSnapshotSignature;
22 import static org.openecomp.sdc.onboarding.BuildHelper.getSourceChecksum;
23 import static org.openecomp.sdc.onboarding.BuildHelper.readState;
24 import static org.openecomp.sdc.onboarding.Constants.ANY_EXT;
25 import static org.openecomp.sdc.onboarding.Constants.CHECKSUM;
26 import static org.openecomp.sdc.onboarding.Constants.COLON;
27 import static org.openecomp.sdc.onboarding.Constants.DOT;
28 import static org.openecomp.sdc.onboarding.Constants.EMPTY_JAR;
29 import static org.openecomp.sdc.onboarding.Constants.GENERATED_SOURCE_CHECKSUM;
30 import static org.openecomp.sdc.onboarding.Constants.INSTRUMENT_ONLY;
31 import static org.openecomp.sdc.onboarding.Constants.INSTRUMENT_WITH_TEST_ONLY;
32 import static org.openecomp.sdc.onboarding.Constants.JAR;
33 import static org.openecomp.sdc.onboarding.Constants.JAVA_EXT;
34 import static org.openecomp.sdc.onboarding.Constants.MAIN;
35 import static org.openecomp.sdc.onboarding.Constants.MAIN_CHECKSUM;
36 import static org.openecomp.sdc.onboarding.Constants.MAIN_SOURCE_CHECKSUM;
37 import static org.openecomp.sdc.onboarding.Constants.PREFIX;
38 import static org.openecomp.sdc.onboarding.Constants.RESOURCES_CHANGED;
39 import static org.openecomp.sdc.onboarding.Constants.RESOURCE_CHECKSUM;
40 import static org.openecomp.sdc.onboarding.Constants.RESOURCE_ONLY;
41 import static org.openecomp.sdc.onboarding.Constants.RESOURCE_WITH_TEST_ONLY;
42 import static org.openecomp.sdc.onboarding.Constants.SHA1;
43 import static org.openecomp.sdc.onboarding.Constants.SKIP_INSTALL;
44 import static org.openecomp.sdc.onboarding.Constants.SKIP_MAIN_SOURCE_COMPILE;
45 import static org.openecomp.sdc.onboarding.Constants.SKIP_PMD;
46 import static org.openecomp.sdc.onboarding.Constants.SKIP_RESOURCE_COLLECTION;
47 import static org.openecomp.sdc.onboarding.Constants.SKIP_TEST_RUN;
48 import static org.openecomp.sdc.onboarding.Constants.SKIP_TEST_SOURCE_COMPILE;
49 import static org.openecomp.sdc.onboarding.Constants.TEST;
50 import static org.openecomp.sdc.onboarding.Constants.TEST_CHECKSUM;
51 import static org.openecomp.sdc.onboarding.Constants.TEST_ONLY;
52 import static org.openecomp.sdc.onboarding.Constants.TEST_RESOURCE_CHECKSUM;
53 import static org.openecomp.sdc.onboarding.Constants.TEST_RESOURCE_ONLY;
54 import static org.openecomp.sdc.onboarding.Constants.TEST_SOURCE_CHECKSUM;
55 import static org.openecomp.sdc.onboarding.Constants.UNICORN;
56
57 import java.io.File;
58 import java.io.IOException;
59 import java.io.UncheckedIOException;
60 import java.nio.file.Files;
61 import java.nio.file.Paths;
62 import java.nio.file.StandardOpenOption;
63 import java.security.NoSuchAlgorithmException;
64 import java.util.Arrays;
65 import java.util.Collection;
66 import java.util.HashMap;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.Optional;
70 import java.util.Set;
71
72 import org.apache.maven.artifact.Artifact;
73 import org.apache.maven.execution.MavenSession;
74 import org.apache.maven.model.Plugin;
75 import org.apache.maven.model.PluginExecution;
76 import org.apache.maven.plugin.AbstractMojo;
77 import org.apache.maven.plugin.InvalidPluginDescriptorException;
78 import org.apache.maven.plugin.MavenPluginManager;
79 import org.apache.maven.plugin.MojoExecutionException;
80 import org.apache.maven.plugin.MojoFailureException;
81 import org.apache.maven.plugin.MojoNotFoundException;
82 import org.apache.maven.plugin.PluginDescriptorParsingException;
83 import org.apache.maven.plugin.PluginResolutionException;
84 import org.apache.maven.plugin.descriptor.MojoDescriptor;
85 import org.apache.maven.plugins.annotations.Component;
86 import org.apache.maven.plugins.annotations.LifecyclePhase;
87 import org.apache.maven.plugins.annotations.Mojo;
88 import org.apache.maven.plugins.annotations.Parameter;
89 import org.apache.maven.plugins.annotations.ResolutionScope;
90 import org.apache.maven.project.MavenProject;
91
92
93 @Mojo(name = "pre-compile-helper", threadSafe = true, defaultPhase = LifecyclePhase.GENERATE_RESOURCES,
94         requiresDependencyResolution = ResolutionScope.TEST)
95 public class PreCompileHelperMojo extends AbstractMojo {
96
97     @Parameter(defaultValue = "${project}")
98     private MavenProject project;
99     @Parameter(defaultValue = "${project.artifact.groupId}:${project.artifact.artifactId}")
100     private String moduleCoordinates;
101     @Parameter(defaultValue = "${session}")
102     private MavenSession session;
103     @Parameter
104     private String excludePackaging;
105     @Parameter
106     private List<String> excludeDependencies;
107     @Parameter
108     private BuildState buildState;
109     @Parameter
110     private File mainSourceLocation;
111     @Parameter
112     private File testSourceLocation;
113     @Parameter
114     private File generatedSourceLocation;
115     @Component
116     private MavenPluginManager pluginManager;
117     @Parameter
118     private File mainResourceLocation;
119     @Parameter
120     private File testResourceLocation;
121     private Map<String, Object> resourceBuildData;
122
123     private static Map<String, String> checksumMap;
124     private long mainChecksum = 0;
125     private long testChecksum = 0;
126     private long resourceChecksum = 0;
127     private long testResourceChecksum = 0;
128     Optional<String> artifactPath;
129
130     static {
131         checksumMap = readCurrentPMDState("pmd.dat");
132     }
133
134     public void execute() throws MojoExecutionException, MojoFailureException {
135
136
137         if (project.getPackaging().equals(excludePackaging)) {
138             return;
139         }
140         init();
141         processPMDCheck();
142         project.getProperties().setProperty(EMPTY_JAR, "");
143         if (!System.getProperties().containsKey(UNICORN)) {
144             return;
145         }
146         resourceChecksum = getChecksum(mainResourceLocation, ANY_EXT);
147         testResourceChecksum = getChecksum(testResourceLocation, ANY_EXT);
148         project.getProperties().setProperty(RESOURCE_CHECKSUM, String.valueOf(resourceChecksum));
149         project.getProperties().setProperty(TEST_RESOURCE_CHECKSUM, String.valueOf(testResourceChecksum));
150         byte[] sourceChecksum = calculateChecksum(mainChecksum, resourceChecksum).getBytes();
151         artifactPath = getArtifactPathInLocalRepo(session.getLocalRepository().getUrl(), project, sourceChecksum);
152
153         boolean isFirstBuild = buildState.getBuildTime(moduleCoordinates) == 0 || !artifactPath.isPresent();
154
155         Map<String, Object> moduleBuildData = getCurrentModuleBuildData();
156         Map<String, Object> lastTimeModuleBuildData = buildState.readModuleBuildData();
157         resourceBuildData = getCurrentResourceBuildData();
158         Map<String, Object> lastTimeResourceBuildData = buildState.readResourceBuildData();
159         generateSyncAlert(lastTimeResourceBuildData != null && (
160                 !resourceBuildData.get(MAIN).equals(lastTimeResourceBuildData.get(MAIN)) || !resourceBuildData.get(TEST)
161                                                                                                               .equals(lastTimeResourceBuildData
162                                                                                                                               .get(TEST)
163                                                                                                                               .toString())));
164         boolean buildDataSameWithPreviousBuild =
165                 isBuildDataSameWithPreviousBuild(lastTimeModuleBuildData, moduleBuildData);
166         boolean resourceMainBuildDataSameWithPreviousBuild =
167                 isResourceMainBuildDataSameWithPreviousBuild(lastTimeResourceBuildData);
168
169         boolean mainToBeCompiled = isCompileNeeded(HashMap.class.cast(moduleBuildData.get(MAIN)).keySet(), isFirstBuild,
170                 buildDataSameWithPreviousBuild);
171
172         boolean resourceDataSame = resourceBuildData.equals(lastTimeResourceBuildData);
173
174         boolean testToBeCompiled =
175                 lastTimeModuleBuildData == null || !moduleBuildData.get(TEST).equals(lastTimeModuleBuildData.get(TEST));
176         final boolean instrumented = isCurrentModuleInstrumented();
177         setMainBuildAttribute(mainToBeCompiled, testToBeCompiled);
178         generateSignature(sourceChecksum);
179         setTestBuild(resourceDataSame, resourceMainBuildDataSameWithPreviousBuild, testToBeCompiled, mainToBeCompiled);
180         setInstrumentedBuild(testToBeCompiled, mainToBeCompiled, instrumented);
181
182         if (!moduleBuildData.equals(lastTimeModuleBuildData) || isFirstBuild) {
183             buildState.addModuleBuildData(moduleCoordinates, moduleBuildData);
184         }
185         setResourceBuild(resourceMainBuildDataSameWithPreviousBuild, mainToBeCompiled, testToBeCompiled);
186         setJarFlags(mainToBeCompiled, instrumented, !resourceMainBuildDataSameWithPreviousBuild);
187         setInstallFlags(mainToBeCompiled, instrumented, project.getPackaging(),
188                 !resourceMainBuildDataSameWithPreviousBuild);
189
190         setArtifactPath(mainToBeCompiled, instrumented, JAR.equals(project.getPackaging()),
191                 resourceMainBuildDataSameWithPreviousBuild);
192     }
193
194     private void generateSignature(byte[] sourceChecksum) {
195         try {
196             Paths.get(project.getBuild().getOutputDirectory()).toFile().mkdirs();
197             Files.write(Paths.get(project.getBuild().getOutputDirectory(), UNICORN + DOT + CHECKSUM), sourceChecksum,
198                     StandardOpenOption.CREATE);
199         } catch (IOException e) {
200             throw new UncheckedIOException(e);
201         }
202     }
203
204     private String calculateChecksum(long mainChecksum, long resourceChecksum) throws MojoExecutionException {
205         try {
206             return getSourceChecksum(mainChecksum + COLON + resourceChecksum, SHA1);
207         } catch (NoSuchAlgorithmException e) {
208             throw new MojoExecutionException(e.getMessage(), e);
209         }
210     }
211
212     private boolean isResourceMainBuildDataSameWithPreviousBuild(Map<String, Object> lastTimeResourceBuildData) {
213         return lastTimeResourceBuildData != null && (lastTimeResourceBuildData.get(MAIN) != null && resourceBuildData
214                                                                                                             .get(MAIN)
215                                                                                                             .equals(lastTimeResourceBuildData
216                                                                                                                             .get(MAIN)));
217     }
218
219     private boolean isBuildDataSameWithPreviousBuild(Map<String, Object> lastTimeModuleBuildData,
220             Map<String, Object> moduleBuildData) {
221         return lastTimeModuleBuildData != null && (lastTimeModuleBuildData.get(MAIN) != null && moduleBuildData
222                                                                                                         .get(MAIN)
223                                                                                                         .equals(lastTimeModuleBuildData
224                                                                                                                         .get(MAIN)));
225     }
226
227     private void setInstrumentedBuild(boolean testToBeCompiled, boolean mainToBeCompiled, boolean instrumented) {
228         if (!testToBeCompiled && !mainToBeCompiled && instrumented) {
229             project.getProperties().setProperty(INSTRUMENT_ONLY, Boolean.TRUE.toString());
230             project.getProperties().remove(SKIP_MAIN_SOURCE_COMPILE);
231             project.getProperties().remove(SKIP_TEST_SOURCE_COMPILE);
232         }
233         if (testToBeCompiled && !mainToBeCompiled && instrumented) {
234             project.getProperties().setProperty(INSTRUMENT_WITH_TEST_ONLY, Boolean.TRUE.toString());
235             project.getProperties().remove(SKIP_MAIN_SOURCE_COMPILE);
236         }
237         if (instrumented) {
238             buildState.markTestsMandatoryModule(moduleCoordinates);
239             project.getProperties().setProperty(SKIP_TEST_RUN, Boolean.FALSE.toString());
240         }
241     }
242
243     private void setArtifactPath(boolean mainToBeCompiled, boolean instrumented, boolean isJar,
244             boolean resourceDataSame) {
245         if (!mainToBeCompiled && !instrumented && isJar && resourceDataSame) {
246             project.getProperties().setProperty("artifactPathToCopy", artifactPath.orElse(null));
247         }
248     }
249
250     private void setResourceBuild(boolean resourceMainBuildDataSameWithPreviousBuild, boolean mainToBeCompiled,
251             boolean testToBeCompiled) {
252         if (resourceMainBuildDataSameWithPreviousBuild) {
253             project.getProperties().setProperty(SKIP_RESOURCE_COLLECTION, Boolean.TRUE.toString());
254         } else {
255             project.getProperties().setProperty(RESOURCES_CHANGED, Boolean.TRUE.toString());
256         }
257         if (!resourceMainBuildDataSameWithPreviousBuild && !mainToBeCompiled) {
258             project.getProperties().remove(SKIP_MAIN_SOURCE_COMPILE);
259             if (!testToBeCompiled) {
260                 project.getProperties().remove(SKIP_TEST_SOURCE_COMPILE);
261                 project.getProperties().setProperty(RESOURCE_ONLY, Boolean.TRUE.toString());
262             } else {
263                 project.getProperties().setProperty(RESOURCE_WITH_TEST_ONLY, Boolean.TRUE.toString());
264             }
265         }
266     }
267
268     private void setTestBuild(boolean resourceDataSame, boolean resourceMainBuildDataSameWithPreviousBuild,
269             boolean testToBeCompiled, boolean mainToBeCompiled) {
270         if (!resourceDataSame) {
271             buildState.addResourceBuildData(moduleCoordinates, resourceBuildData);
272             project.getProperties().setProperty(SKIP_TEST_RUN, Boolean.FALSE.toString());
273             if (resourceMainBuildDataSameWithPreviousBuild && !testToBeCompiled && !mainToBeCompiled) {
274                 project.getProperties().setProperty(TEST_RESOURCE_ONLY, Boolean.TRUE.toString());
275                 project.getProperties().remove(SKIP_MAIN_SOURCE_COMPILE);
276                 project.getProperties().remove(SKIP_TEST_SOURCE_COMPILE);
277             }
278         }
279     }
280
281     private void setMainBuildAttribute(boolean mainToBeCompiled, boolean testToBeCompiled) {
282         if (!mainToBeCompiled) {
283             project.getProperties().setProperty(SKIP_MAIN_SOURCE_COMPILE, Boolean.TRUE.toString());
284         }
285         if (testToBeCompiled && !mainToBeCompiled) {
286             project.getProperties().setProperty(TEST_ONLY, Boolean.TRUE.toString());
287             project.getProperties().remove(SKIP_MAIN_SOURCE_COMPILE);
288         }
289
290         if (mainToBeCompiled || testToBeCompiled) {
291             project.getProperties().setProperty(SKIP_TEST_RUN, Boolean.FALSE.toString());
292         } else {
293             project.getProperties().setProperty(SKIP_TEST_SOURCE_COMPILE, Boolean.TRUE.toString());
294         }
295     }
296
297     private void setJarFlags(boolean compile, boolean instrumented, boolean resourceChanged) {
298         if (compile || instrumented || resourceChanged || PREFIX == UNICORN) {
299             project.getProperties().setProperty(EMPTY_JAR, "");
300         } else {
301             project.getProperties().setProperty(EMPTY_JAR, "**/*");
302             project.getProperties().setProperty("mvnDsc", "false");
303         }
304     }
305
306     private void setInstallFlags(boolean compile, boolean instrumented, String packaging, boolean resourceChanged) {
307         if (!compile && !instrumented && !resourceChanged && JAR.equals(packaging)) {
308             project.getProperties().setProperty(SKIP_INSTALL, Boolean.TRUE.toString());
309         }
310     }
311
312     private boolean isCompileNeeded(Collection<String> dependencyCoordinates, boolean isFirstBuild,
313             boolean buildDataSame) {
314         return isFirstBuild || !buildDataSame || buildState.isCompileMust(moduleCoordinates, dependencyCoordinates);
315     }
316
317     private boolean isCurrentModuleInstrumented() {
318         try {
319             return scanModuleFor(LifecyclePhase.PROCESS_CLASSES.id(), LifecyclePhase.PROCESS_TEST_CLASSES.id(),
320                     LifecyclePhase.COMPILE.id(), LifecyclePhase.TEST_COMPILE.id());
321         } catch (Exception e) {
322             getLog().debug(e);
323             return true;
324         }
325     }
326
327     boolean isCodeGenerator() {
328         try {
329             return scanModuleFor(LifecyclePhase.GENERATE_RESOURCES.id(), LifecyclePhase.GENERATE_SOURCES.id(),
330                     LifecyclePhase.GENERATE_TEST_RESOURCES.id(), LifecyclePhase.GENERATE_TEST_SOURCES.id());
331         } catch (Exception e) {
332             getLog().debug(e);
333             return true;
334         }
335     }
336
337     private Map<String, Object> getCurrentModuleBuildData() throws MojoExecutionException {
338         Map<String, Object> moduleBuildData = new HashMap<>();
339         moduleBuildData.put(MAIN, new HashMap<String, String>());
340         moduleBuildData.put(TEST, new HashMap<String, String>());
341         HashMap.class.cast(moduleBuildData.get(MAIN))
342                      .put(MAIN_SOURCE_CHECKSUM, project.getProperties().getProperty(MAIN_CHECKSUM));
343         HashMap.class.cast(moduleBuildData.get(TEST))
344                      .put(TEST_SOURCE_CHECKSUM, project.getProperties().getProperty(TEST_CHECKSUM));
345         if (isCodeGenerator()) {
346             HashMap.class.cast(moduleBuildData.get(MAIN))
347                          .put(GENERATED_SOURCE_CHECKSUM, getChecksum(generatedSourceLocation, JAVA_EXT));
348         }
349         if (project.getArtifacts() == null || project.getArtifacts().isEmpty()) {
350             return moduleBuildData;
351         }
352         for (Artifact dependency : project.getArtifacts()) {
353             if (JAR.equals(dependency.getType())) {
354                 String version = dependency.isSnapshot() || dependency.getFile().getName().contains("SNAPSHOT") ?
355                                          getSnapshotSignature(dependency.getFile(),
356                                                  dependency.getGroupId() + COLON + dependency.getArtifactId(),
357                                                  dependency.getVersion()) : dependency.getVersion();
358                 if (excludeDependencies.contains(dependency.getScope())) {
359                     HashMap.class.cast(moduleBuildData.get(TEST))
360                                  .put(dependency.getGroupId() + COLON + dependency.getArtifactId(), version);
361                     continue;
362                 }
363                 HashMap.class.cast(moduleBuildData.get(MAIN))
364                              .put(dependency.getGroupId() + COLON + dependency.getArtifactId(), version);
365             }
366         }
367         return moduleBuildData;
368     }
369
370     private static Map<String, String> readCurrentPMDState(String fileName) {
371         Optional<HashMap> val = readState(fileName, HashMap.class);
372         return val.orElseGet(HashMap::new);
373     }
374
375     private boolean isPMDMandatory(Set<Artifact> dependencies) {
376         for (Artifact artifact : dependencies) {
377             if (BuildState.isPMDRun(artifact.getGroupId() + COLON + artifact.getArtifactId())) {
378                 return true;
379             }
380         }
381         return false;
382     }
383
384     private boolean scanModuleFor(String... types)
385             throws InvalidPluginDescriptorException, PluginResolutionException, MojoNotFoundException,
386                            PluginDescriptorParsingException {
387         for (Plugin plugin : project.getBuildPlugins()) {
388             if (!("org.apache.maven.plugins".equals(plugin.getGroupId()) && plugin.getArtifactId().startsWith("maven"))
389                         && !plugin.getGroupId().startsWith("org.openecomp.sdc")) {
390                 boolean success = scanPlugin(plugin, types);
391                 if (success) {
392                     return true;
393                 }
394             }
395         }
396         return false;
397     }
398
399     private boolean scanPlugin(Plugin plugin, String... types)
400             throws InvalidPluginDescriptorException, PluginDescriptorParsingException, MojoNotFoundException,
401                            PluginResolutionException {
402         for (PluginExecution pluginExecution : plugin.getExecutions()) {
403             if (pluginExecution.getPhase() != null) {
404                 boolean phaseAvailable = Arrays.asList(types).contains(pluginExecution.getPhase());
405                 if (phaseAvailable) {
406                     return true;
407                 }
408             }
409             for (String goal : pluginExecution.getGoals()) {
410                 MojoDescriptor md = pluginManager.getMojoDescriptor(plugin, goal, project.getRemotePluginRepositories(),
411                         session.getRepositorySession());
412                 if (Arrays.asList(types).contains(md.getPhase())) {
413                     return true;
414                 }
415             }
416         }
417         return false;
418     }
419
420     private Map<String, Object> getCurrentResourceBuildData() {
421         HashMap<String, Object> resourceBuildStateData = new HashMap<>();
422         resourceBuildStateData.put(MAIN, project.getProperties().getProperty(RESOURCE_CHECKSUM));
423         resourceBuildStateData.put(TEST, project.getProperties().getProperty(TEST_RESOURCE_CHECKSUM));
424         resourceBuildStateData.put("dependency", getDependencies().hashCode());
425         return resourceBuildStateData;
426     }
427
428     private Map<String, String> getDependencies() {
429         Map<String, String> dependencies = new HashMap<>();
430         for (Artifact d : project.getArtifacts()) {
431             dependencies.put(d.getGroupId() + COLON + d.getArtifactId(), d.getVersion());
432         }
433         return dependencies;
434     }
435
436     private void init() {
437         if (mainSourceLocation == null) {
438             mainSourceLocation = Paths.get(project.getBuild().getSourceDirectory()).toFile();
439         }
440         if (testSourceLocation == null) {
441             testSourceLocation = Paths.get(project.getBuild().getTestSourceDirectory()).toFile();
442         }
443         if (mainResourceLocation == null) {
444             mainResourceLocation = Paths.get(project.getBuild().getResources().get(0).getDirectory()).toFile();
445         }
446         if (testResourceLocation == null) {
447             testResourceLocation = Paths.get(project.getBuild().getTestResources().get(0).getDirectory()).toFile();
448         }
449     }
450
451     private void processPMDCheck() {
452         mainChecksum = getChecksum(mainSourceLocation, JAVA_EXT);
453         testChecksum = getChecksum(testSourceLocation, JAVA_EXT);
454         project.getProperties().setProperty(MAIN_CHECKSUM, String.valueOf(mainChecksum));
455         project.getProperties().setProperty(TEST_CHECKSUM, String.valueOf(testChecksum));
456         String checksum = mainChecksum + COLON + testChecksum;
457         if (!checksum.equals(checksumMap.get(moduleCoordinates)) || isPMDMandatory(project.getArtifacts())) {
458             project.getProperties().setProperty(SKIP_PMD, Boolean.FALSE.toString());
459             BuildState.recordPMDRun(moduleCoordinates);
460             generateSyncAlert(!checksum.equals(checksumMap.get(moduleCoordinates)));
461         }
462     }
463
464     private void generateSyncAlert(boolean required) {
465         if (required) {
466             getLog().warn(
467                     "\u001B[33m\u001B[1m UNICORN Alert!!! Source code in version control system for this module is different than your local one. \u001B[0m");
468         }
469     }
470 }