Skip to content

Commit c629f6d

Browse files
authored
fix: improve project folder detection in annotation processor (#733)
1 parent 79eafb7 commit c629f6d

File tree

5 files changed

+411
-95
lines changed

5 files changed

+411
-95
lines changed

core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/preview/PreviewStage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,6 @@ public AbstractBuilder<T> setParallel(boolean parallel) {
303303
* @throws RuntimeException if the name is not set or no changes are provided
304304
*/
305305
public T build() {
306-
307306
Collection<File> resourcesDirectories = new LinkedList<>();
308307

309308
if (resourcesDir != null) {
@@ -321,6 +320,7 @@ public T build() {
321320
.forEach(resourcesDirectories::add);
322321
}
323322

323+
324324
if (name == null || name.isEmpty()) {
325325
throw new RuntimeException("Stage requires name");
326326
}

core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/util/LoggerPreProcessor.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ public class LoggerPreProcessor {
2424

2525
private final ProcessingEnvironment processingEnv;
2626

27+
private final boolean verboseEnabled;
28+
2729
public LoggerPreProcessor(ProcessingEnvironment processingEnv) {
2830
this.processingEnv = processingEnv;
31+
this.verboseEnabled = "true".equals(processingEnv.getOptions().get("flamingock.verbose"));
2932
}
3033

3134
public void info(String message) {
@@ -39,4 +42,10 @@ public void warn(String message) {
3942
public void error(String message) {
4043
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "\t" + logPrefix + message);
4144
}
45+
46+
public void verbose(String message) {
47+
if (verboseEnabled) {
48+
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "\t " + logPrefix + message);
49+
}
50+
}
4251
}

core/flamingock-processor/src/main/java/io/flamingock/core/processor/FlamingockAnnotationProcessor.java

Lines changed: 86 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import io.flamingock.api.annotations.EnableFlamingock;
2121
import io.flamingock.api.annotations.Stage;
2222
import io.flamingock.core.processor.util.AnnotationFinder;
23+
import io.flamingock.core.processor.util.PathResolver;
24+
import io.flamingock.core.processor.util.ProjectRootDetector;
2325
import io.flamingock.internal.common.core.preview.CodePreviewChange;
2426
import io.flamingock.internal.common.core.util.LoggerPreProcessor;
2527
import io.flamingock.internal.common.core.util.Serializer;
@@ -165,17 +167,15 @@ public FlamingockAnnotationProcessor() {
165167
public synchronized void init(ProcessingEnvironment processingEnv) {
166168
super.init(processingEnv);
167169
logger = new LoggerPreProcessor(processingEnv);
168-
logger.info("Starting EnableFlamingock annotation processor initialization.");
170+
logger.verbose("Starting annotation processor initialization");
169171
resourcesRoot = getResourcesRoot();
170172
sourceRoots = getSourcesPathList();
171-
172-
173-
logger.info("Initialization completed. Pipeline will be processed with @EnableFlamingock annotation.");
173+
logger.verbose("Initialization completed");
174174
}
175175

176176
@Override
177177
public Set<String> getSupportedOptions() {
178-
return new HashSet<>(Arrays.asList("sources", "resources"));
178+
return new HashSet<>(Arrays.asList("sources", "resources", "flamingock.verbose"));
179179
}
180180

181181
@Override
@@ -189,14 +189,14 @@ public Set<String> getSupportedAnnotationTypes() {
189189
@Override
190190
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
191191
if (roundEnv.processingOver()) {
192-
logger.info("Final processing round detected - skipping execution.");
192+
logger.verbose("Final processing round - skipping");
193193
return false;
194194
}
195195
if (hasProcessed) {
196-
logger.info("Changes already processed - skipping execution.");
197196
return true;
198197
}
199198

199+
logger.info("Processing pipeline configuration");
200200
AnnotationFinder annotationFinder = new AnnotationFinder(roundEnv, logger);
201201
EnableFlamingock flamingockAnnotation = annotationFinder.getPipelineAnnotation()
202202
.orElseThrow(() -> new RuntimeException("@EnableFlamingock annotation is mandatory. Please annotate a class with @EnableFlamingock to configure the pipeline."));
@@ -214,7 +214,20 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
214214
String configFile = flamingockAnnotation.configFile();
215215
FlamingockMetadata flamingockMetadata = new FlamingockMetadata(pipeline, setup, configFile);
216216
serializer.serializeFullPipeline(flamingockMetadata);
217-
logger.info("Finished processing annotated classes and generating metadata.");
217+
218+
// Generate summary - count all changes from the final pipeline (code-based + template-based)
219+
int totalStages = pipeline.getStages().size() + (pipeline.getSystemStage() != null ? 1 : 0);
220+
int totalChanges = 0;
221+
if (pipeline.getSystemStage() != null && pipeline.getSystemStage().getTasks() != null) {
222+
totalChanges += pipeline.getSystemStage().getTasks().size();
223+
}
224+
for (PreviewStage stage : pipeline.getStages()) {
225+
if (stage.getTasks() != null) {
226+
totalChanges += stage.getTasks().size();
227+
}
228+
}
229+
logger.info("Generated metadata: " + totalStages + " stages, " + totalChanges + " changes");
230+
218231
hasProcessed = true;
219232
return true;
220233
}
@@ -381,15 +394,22 @@ private SystemPreviewStage mapAnnotationToSystemStage(Map<String, List<CodePrevi
381394
throw new RuntimeException("@Stage annotation with type SYSTEM requires a location. Please specify the location field.");
382395
}
383396

397+
logger.verbose("Building stage: SystemStage");
398+
384399
String sourcesPackage = null;
385400
String resourcesDir = null;
386401
Collection<CodePreviewChange> changeClasses = null;
387402

388-
if (isPackageName(location)) {
403+
if (PathResolver.isPackageName(location)) {
389404
sourcesPackage = location;
390405
changeClasses = codedChangesByPackage.get(sourcesPackage);
406+
logger.verbose("Sources package: " + sourcesPackage);
407+
if (changeClasses != null) {
408+
logger.verbose("Found " + changeClasses.size() + " code-based changes in " + sourcesPackage);
409+
}
391410
} else {
392-
resourcesDir = processResourceLocation(location);
411+
resourcesDir = PathResolver.processResourceLocation(location);
412+
logger.verbose("Resources directory: " + resourcesDir);
393413
}
394414

395415
// For system stage, use hardcoded name and description to maintain consistency
@@ -411,21 +431,28 @@ private PreviewStage mapAnnotationToStage(Map<String, List<CodePreviewChange>> c
411431
throw new RuntimeException("@Stage annotation requires a location. Please specify the location field.");
412432
}
413433

434+
// Derive name from location if not provided
435+
String name = stageAnnotation.name();
436+
if (name.isEmpty()) {
437+
name = PathResolver.deriveNameFromLocation(location);
438+
}
439+
440+
logger.verbose("Building stage: " + name);
441+
414442
String sourcesPackage = null;
415443
String resourcesDir = null;
416444
Collection<CodePreviewChange> changeClasses = null;
417445

418-
if (isPackageName(location)) {
446+
if (PathResolver.isPackageName(location)) {
419447
sourcesPackage = location;
420448
changeClasses = codedChangesByPackage.get(sourcesPackage);
449+
logger.verbose("Sources package: " + sourcesPackage);
450+
if (changeClasses != null) {
451+
logger.verbose("Found " + changeClasses.size() + " code-based changes in " + sourcesPackage);
452+
}
421453
} else {
422-
resourcesDir = processResourceLocation(location);
423-
}
424-
425-
// Derive name from location if not provided
426-
String name = stageAnnotation.name();
427-
if (name.isEmpty()) {
428-
name = deriveNameFromLocation(location);
454+
resourcesDir = PathResolver.processResourceLocation(location);
455+
logger.verbose("Resources directory: " + resourcesDir);
429456
}
430457

431458
return PreviewStage.defaultBuilder(stageAnnotation.type())
@@ -568,15 +595,22 @@ private SystemPreviewStage mapToSystemStage(Map<String, List<CodePreviewChange>>
568595
throw new RuntimeException("System stage in YAML pipeline requires a 'location' field. Please specify the location where changes are found.");
569596
}
570597

598+
logger.verbose("Building stage: SystemStage");
599+
571600
String sourcesPackage = null;
572601
String resourcesDir = null;
573602
Collection<CodePreviewChange> changeClasses = null;
574603

575-
if (isPackageName(location)) {
604+
if (PathResolver.isPackageName(location)) {
576605
sourcesPackage = location;
577606
changeClasses = codedChangesByPackage.get(sourcesPackage);
607+
logger.verbose("Sources package: " + sourcesPackage);
608+
if (changeClasses != null) {
609+
logger.verbose("Found " + changeClasses.size() + " code-based changes in " + sourcesPackage);
610+
}
578611
} else {
579-
resourcesDir = processResourceLocation(location);
612+
resourcesDir = PathResolver.processResourceLocation(location);
613+
logger.verbose("Resources directory: " + resourcesDir);
580614
}
581615

582616
// For system stage, use hardcoded name and description to maintain consistency
@@ -599,20 +633,27 @@ private PreviewStage mapToStage(Map<String, List<CodePreviewChange>> codedChange
599633
throw new RuntimeException("Stage in YAML pipeline requires a 'location' field. Please specify the location where changes are found.");
600634
}
601635

636+
String name = stageMap.get("name");
637+
if (name == null || name.trim().isEmpty()) {
638+
name = PathResolver.deriveNameFromLocation(location);
639+
}
640+
641+
logger.verbose("Building stage: " + name);
642+
602643
String sourcesPackage = null;
603644
String resourcesDir = null;
604645
Collection<CodePreviewChange> changeClasses = null;
605646

606-
if (isPackageName(location)) {
647+
if (PathResolver.isPackageName(location)) {
607648
sourcesPackage = location;
608649
changeClasses = codedChangesByPackage.get(sourcesPackage);
650+
logger.verbose("Sources package: " + sourcesPackage);
651+
if (changeClasses != null) {
652+
logger.verbose("Found " + changeClasses.size() + " code-based changes in " + sourcesPackage);
653+
}
609654
} else {
610-
resourcesDir = processResourceLocation(location);
611-
}
612-
613-
String name = stageMap.get("name");
614-
if (name == null || name.trim().isEmpty()) {
615-
name = deriveNameFromLocation(location);
655+
resourcesDir = PathResolver.processResourceLocation(location);
656+
logger.verbose("Resources directory: " + resourcesDir);
616657
}
617658

618659
return PreviewStage.defaultBuilder(StageType.from(stageMap.get("type")))
@@ -628,91 +669,42 @@ private PreviewStage mapToStage(Map<String, List<CodePreviewChange>> codedChange
628669

629670
@NotNull
630671
private List<String> getSourcesPathList() {
672+
// Priority 1: Use explicitly provided parameter
631673
if (processingEnv.getOptions().containsKey(SOURCES_PATH_ARG)) {
632674
String sourcesPath = processingEnv.getOptions().get(SOURCES_PATH_ARG);
633-
logger.info("'" + SOURCES_PATH_ARG + "' parameter passed: '" + sourcesPath + "'");
675+
logger.verbose("Using explicitly provided sources path: " + sourcesPath);
634676
return Collections.singletonList(sourcesPath);
635-
} else {
636-
logger.warn("'" + SOURCES_PATH_ARG + "' parameter NOT passed. Searching in: '" + DEFAULT_SOURCE_DIRS + "'");
637-
return DEFAULT_SOURCE_DIRS;
638677
}
678+
679+
// Priority 2: Auto-detect project root and convert to absolute paths
680+
File projectRoot = ProjectRootDetector.detectProjectRoot(processingEnv);
681+
if (projectRoot != null) {
682+
logger.info("Auto-detected project root: " + projectRoot.getAbsolutePath());
683+
List<String> absolutePaths = ProjectRootDetector.toAbsoluteSourcePaths(projectRoot, DEFAULT_SOURCE_DIRS);
684+
logger.verbose("Source paths: " + absolutePaths);
685+
return absolutePaths;
686+
}
687+
688+
// Priority 3: Fall back to relative paths
689+
logger.warn("Could not auto-detect project root, using relative paths (may fail in some IDEs)");
690+
logger.verbose("Using relative source paths: " + DEFAULT_SOURCE_DIRS);
691+
return DEFAULT_SOURCE_DIRS;
639692
}
640693

641694
@NotNull
642695
private String getResourcesRoot() {
643696
final String resourcesDir;
644697
if (processingEnv.getOptions().containsKey(RESOURCES_PATH_ARG)) {
645698
resourcesDir = processingEnv.getOptions().get(RESOURCES_PATH_ARG);
646-
logger.info("'" + RESOURCES_PATH_ARG + "' parameter passed: '" + resourcesDir + "'");
699+
logger.verbose("Using explicitly provided resources path: " + resourcesDir);
647700
} else {
648701
resourcesDir = DEFAULT_RESOURCES_PATH;
649-
logger.warn("'" + RESOURCES_PATH_ARG + "' parameter NOT passed. Using default '" + resourcesDir + "'");
702+
logger.verbose("Resources root: " + resourcesDir);
650703
}
651704
return resourcesDir;
652705
}
653706

654-
/**
655-
* Determines if the given location string represents a package name.
656-
* A package name contains dots and no slashes (e.g., "com.example.migrations").
657-
*
658-
* @param location the location string to check
659-
* @return true if the location is a package name, false otherwise
660-
*/
661-
private boolean isPackageName(String location) {
662-
return location.contains(".") && !location.contains("/");
663-
}
664-
665-
666-
/**
667-
* Derives a stage name from the location string by extracting the last segment.
668-
* Examples:
669-
* - "com.example.migrations" → "migrations"
670-
* - "resources/db/migrations" → "migrations"
671-
* - "/absolute/path/to/migrations" → "migrations"
672-
*
673-
* @param location the location string
674-
* @return the derived name
675-
*/
676-
private String deriveNameFromLocation(String location) {
677-
678-
// Remove "resources/" prefix if present
679-
String cleanLocation = location;
680-
if (cleanLocation.startsWith("resources/")) {
681-
cleanLocation = cleanLocation.substring("resources/".length());
682-
}
683-
684-
// Split by either dots (for packages) or slashes (for paths)
685-
String[] segments;
686-
if (cleanLocation.contains(".") && !cleanLocation.contains("/")) {
687-
segments = cleanLocation.split("\\.");
688-
} else {
689-
segments = cleanLocation.split("/");
690-
}
691707

692-
// Get the last non-empty segment
693-
for (int i = segments.length - 1; i >= 0; i--) {
694-
String segment = segments[i].trim();
695-
if (!segment.isEmpty()) {
696-
return segment;
697-
}
698-
}
699-
700-
return location;
701-
}
702-
703-
/**
704-
* Processes a resource location to handle potential "resources/" prefix.
705-
* Strips "resources/" prefix if present to avoid double-prefixing when
706-
* concatenated with resourcesRoot ("src/main/resources").
707-
*
708-
* @param location the location string from user input
709-
* @return processed location for use as resourcesDir
710-
*/
711-
private String processResourceLocation(String location) {
712-
return location != null && location.startsWith("resources/")
713-
? location.substring("resources/".length())
714-
: location;
715-
}
716708

717709
private void validateConfiguration(EnableFlamingock pipelineAnnotation, boolean hasFileInAnnotation, boolean hasStagesInAnnotation) {
718710
if (hasFileInAnnotation && hasStagesInAnnotation) {

0 commit comments

Comments
 (0)