2626import java .nio .file .Path ;
2727import java .nio .file .Paths ;
2828import java .util .ArrayList ;
29- import java .util .Arrays ;
3029import java .util .Collection ;
3130import java .util .HashMap ;
3231import java .util .HashSet ;
4544import java .util .concurrent .atomic .AtomicReference ;
4645import java .util .function .Supplier ;
4746import java .util .function .UnaryOperator ;
47+ import java .util .regex .Matcher ;
48+ import java .util .regex .Pattern ;
4849import java .util .stream .Collectors ;
4950import java .util .stream .Stream ;
5051
@@ -232,6 +233,8 @@ public ModelBuilderResult build(ModelBuilderRequest request) throws ModelBuilder
232233 }
233234
234235 protected class DefaultModelBuilderSession implements ModelProblemCollector {
236+ private static final Pattern REGEX = Pattern .compile ("\\ $\\ {([^}]+)}" );
237+
235238 final Session session ;
236239 final ModelBuilderRequest request ;
237240 final DefaultModelBuilderResult result ;
@@ -562,58 +565,15 @@ public void mergeRepositories(List<Repository> toAdd, boolean replace) {
562565 }
563566
564567 //
565- // Transform raw model to build pom
566- //
567- Model transformFileToRaw (Model model ) {
568- Model .Builder builder = Model .newBuilder (model );
569- builder = handleParent (model , builder );
570- builder = handleReactorDependencies (model , builder );
571- builder = handleCiFriendlyVersion (model , builder );
572- return builder .build ();
573- }
574-
575- //
576- // Infer parent information
577- //
578- Model .Builder handleParent (Model model , Model .Builder builder ) {
579- Parent parent = model .getParent ();
580- if (parent != null ) {
581- String version = parent .getVersion ();
582- String modVersion = replaceCiFriendlyVersion (version );
583- if (!Objects .equals (version , modVersion )) {
584- if (builder == null ) {
585- builder = Model .newBuilder (model );
586- }
587- builder .parent (parent .withVersion (modVersion ));
588- }
589- }
590- return builder ;
591- }
592-
593- //
594- // CI friendly versions
595- //
596- Model .Builder handleCiFriendlyVersion (Model model , Model .Builder builder ) {
597- String version = model .getVersion ();
598- String modVersion = replaceCiFriendlyVersion (version );
599- if (!Objects .equals (version , modVersion )) {
600- if (builder == null ) {
601- builder = Model .newBuilder (model );
602- }
603- builder .version (modVersion );
604- }
605- return builder ;
606- }
607-
608- //
568+ // Transform raw model to build pom.
609569 // Infer inner reactor dependencies version
610570 //
611- Model . Builder handleReactorDependencies (Model model , Model . Builder builder ) {
571+ Model transformFileToRaw (Model model ) {
612572 List <Dependency > newDeps = new ArrayList <>();
613573 boolean modified = false ;
614574 for (Dependency dep : model .getDependencies ()) {
615- Dependency .Builder depBuilder = null ;
616575 if (dep .getVersion () == null ) {
576+ Dependency .Builder depBuilder = null ;
617577 Model depModel = getRawModel (model .getPomFile (), dep .getGroupId (), dep .getArtifactId ());
618578 if (depModel != null ) {
619579 String version = depModel .getVersion ();
@@ -634,30 +594,35 @@ Model.Builder handleReactorDependencies(Model model, Model.Builder builder) {
634594 depBuilder .groupId (depGroupId ).location ("groupId" , groupIdLocation );
635595 }
636596 }
597+ if (depBuilder != null ) {
598+ newDeps .add (depBuilder .build ());
599+ modified = true ;
600+ } else {
601+ newDeps .add (dep );
602+ }
637603 }
638- if (depBuilder != null ) {
639- newDeps .add (depBuilder .build ());
640- modified = true ;
641- } else {
642- newDeps .add (dep );
643- }
644- }
645- if (modified ) {
646- if (builder == null ) {
647- builder = Model .newBuilder (model );
648- }
649- builder .dependencies (newDeps );
650604 }
651- return builder ;
605+ return modified ? model . withDependencies ( newDeps ) : model ;
652606 }
653607
654- String replaceCiFriendlyVersion (String version ) {
608+ String replaceCiFriendlyVersion (Map <String , String > properties , String version ) {
609+ // TODO: we're using a simple regex here, but we should probably use
610+ // a proper interpolation service to do the replacements
611+ // once one is available in maven-api-impl
612+ // https://issues.apache.org/jira/browse/MNG-8262
655613 if (version != null ) {
656- for (String key : Arrays .asList ("changelist" , "revision" , "sha1" )) {
657- String val = request .getUserProperties ().get (key );
658- if (val != null ) {
659- version = version .replace ("${" + key + "}" , val );
660- }
614+ Matcher matcher = REGEX .matcher (version );
615+ if (matcher .find ()) {
616+ StringBuilder result = new StringBuilder ();
617+ do {
618+ // extract the key inside ${}
619+ String key = matcher .group (1 );
620+ // get replacement from the map, or use the original ${xy} if not found
621+ String replacement = properties .getOrDefault (key , "\\ " + matcher .group (0 ));
622+ matcher .appendReplacement (result , replacement );
623+ } while (matcher .find ());
624+ matcher .appendTail (result ); // Append the remaining part of the string
625+ return result .toString ();
661626 }
662627 }
663628 return version ;
@@ -738,7 +703,6 @@ Stream<DefaultModelBuilderResult> results(DefaultModelBuilderResult r) {
738703 return Stream .concat (Stream .of (r ), r .getChildren ().stream ().flatMap (this ::results ));
739704 }
740705
741- @ SuppressWarnings ("checkstyle:MethodLength" )
742706 private void loadFromRoot (Path root , Path top ) {
743707 try (PhasingExecutor executor = createExecutor ()) {
744708 DefaultModelBuilderResult r = Objects .equals (top , root ) ? result : new DefaultModelBuilderResult ();
@@ -1225,16 +1189,18 @@ Model readFileModel() throws ModelBuilderException {
12251189 Model doReadFileModel () throws ModelBuilderException {
12261190 ModelSource modelSource = request .getSource ();
12271191 Model model ;
1192+ Path rootDirectory ;
12281193 setSource (modelSource .getLocation ());
12291194 logger .debug ("Reading file model from " + modelSource .getLocation ());
12301195 try {
12311196 boolean strict = request .getRequestType () == ModelBuilderRequest .RequestType .BUILD_POM ;
1232- // TODO: we do cache, but what if strict does not have the same value?
1233- Path rootDirectory ;
12341197 try {
12351198 rootDirectory = request .getSession ().getRootDirectory ();
12361199 } catch (IllegalStateException ignore ) {
12371200 rootDirectory = modelSource .getPath ();
1201+ while (rootDirectory != null && !Files .isDirectory (rootDirectory )) {
1202+ rootDirectory = rootDirectory .getParent ();
1203+ }
12381204 }
12391205 try (InputStream is = modelSource .openStream ()) {
12401206 model = modelProcessor .read (XmlReaderRequest .builder ()
@@ -1372,6 +1338,29 @@ Model doReadFileModel() throws ModelBuilderException {
13721338 add (Severity .FATAL , ModelProblem .Version .V41 , "Error discovering subprojects" , e );
13731339 }
13741340 }
1341+
1342+ // CI friendly version
1343+ // All expressions are interpolated using user properties and properties
1344+ // defined on the root project.
1345+ Map <String , String > properties = new HashMap <>();
1346+ if (!Objects .equals (rootDirectory , model .getProjectDirectory ())) {
1347+ Model rootModel = derive (ModelSource .fromPath (modelProcessor .locateExistingPom (rootDirectory )))
1348+ .readFileModel ();
1349+ properties .putAll (rootModel .getProperties ());
1350+ } else {
1351+ properties .putAll (model .getProperties ());
1352+ }
1353+ properties .putAll (session .getUserProperties ());
1354+ model = model .with ()
1355+ .version (replaceCiFriendlyVersion (properties , model .getVersion ()))
1356+ .parent (
1357+ model .getParent () != null
1358+ ? model .getParent ()
1359+ .withVersion (replaceCiFriendlyVersion (
1360+ properties ,
1361+ model .getParent ().getVersion ()))
1362+ : null )
1363+ .build ();
13751364 }
13761365
13771366 for (var transformer : transformers ) {
0 commit comments