001/* 002 * JDrupes Builder 003 * Copyright (C) 2025 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.builder.startup; 020 021import java.net.MalformedURLException; 022import java.net.URI; 023import java.net.URL; 024import java.net.URLClassLoader; 025import java.util.Arrays; 026import java.util.Collections; 027import java.util.Optional; 028import java.util.logging.Logger; 029import java.util.stream.Stream; 030import org.jdrupes.builder.api.BuildException; 031import org.jdrupes.builder.api.FileResource; 032import org.jdrupes.builder.api.FileTree; 033import static org.jdrupes.builder.api.Intend.*; 034import org.jdrupes.builder.api.Launcher; 035import org.jdrupes.builder.api.Resource; 036import org.jdrupes.builder.api.ResourceRequest; 037import static org.jdrupes.builder.api.ResourceRequest.*; 038import org.jdrupes.builder.api.RootProject; 039import org.jdrupes.builder.core.LauncherSupport; 040import static org.jdrupes.builder.java.JavaTypes.*; 041import org.jdrupes.builder.mvnrepo.MvnRepoLookup; 042 043/// A default implementation of a [Launcher]. The launcher first builds 044/// the user's JDrupes Builder project, using the JDrupes Builder project 045/// defined by [BootstrapRoot] and [BootstrapBuild]. The default action 046/// of [BootstrapRoot] adds the results from the bootstrap build 047/// to the classpath and launches the actual JDrupes Builder project. 048/// 049public class BootstrapLauncher extends AbstractLauncher { 050 051 /// The log. 052 protected final Logger log = Logger.getLogger(getClass().getName()); 053 private RootProject rootProject; 054 055 /// Instantiates a new bootstrap launcher. An instance of the class 056 /// passed as argument is created and used as root project for the 057 /// build. 058 /// 059 /// Unless the root project is the only project, the root project 060 /// must declare dependencies, else the subprojects won't be 061 /// instantiated. 062 /// 063 /// @param rootPrjCls the root project 064 /// @param args the args 065 /// 066 @SuppressWarnings("PMD.UseVarargs") 067 public BootstrapLauncher( 068 Class<? extends RootProject> rootPrjCls, String[] args) { 069 super(args); 070 unwrapBuildException(() -> { 071 rootProject = LauncherSupport.createProjects( 072 rootPrjCls, Collections.emptyList(), jdbldProps, commandLine); 073 074 // Add build extensions to the build project. 075 var mvnLookup = new MvnRepoLookup(); 076 Optional.ofNullable(jdbldProps 077 .getProperty(BootstrapBuild.EXTENSIONS_SNAPSHOT_REPO, null)) 078 .map(URI::create).ifPresent(mvnLookup::snapshotRepository); 079 var buildCoords = Arrays.asList(jdbldProps 080 .getProperty(BootstrapBuild.BUILD_EXTENSIONS, "").split(",")) 081 .stream().map(String::trim).filter(c -> !c.isBlank()).toList(); 082 log.fine(() -> "Adding libraries from " + buildCoords 083 + " to classpath for builder project compilation"); 084 buildCoords.forEach(mvnLookup::resolve); 085 rootProject.project(BootstrapBuild.class).dependency(Expose, 086 mvnLookup); 087 var cpUrls = rootProject.get(requestFor(CompilationClasspathType)) 088 .map(cpe -> { 089 try { 090 if (cpe instanceof FileTree tree) { 091 return tree.root().toFile().toURI().toURL(); 092 } 093 return ((FileResource) cpe).path().toFile().toURI() 094 .toURL(); 095 } catch (MalformedURLException e) { 096 // Cannot happen 097 throw new BuildException(e); 098 } 099 }).toArray(URL[]::new); 100 log.fine(() -> "Launching build project with classpath: " 101 + Arrays.toString(cpUrls)); 102 new DirectLauncher( 103 new URLClassLoader(cpUrls, getClass().getClassLoader()), args); 104 return null; 105 }); 106 } 107 108 @Override 109 public <T extends Resource> Stream<T> provide(ResourceRequest<T> request) { 110 return unwrapBuildException(() -> { 111 // Provide requested resource, handling all exceptions here 112 var result = rootProject.get(request).toList(); 113 return result.stream(); 114 }); 115 } 116 117 /// The main method. 118 /// 119 /// @param args the arguments 120 /// 121 public static void main(String[] args) { 122 new BootstrapLauncher(BootstrapRoot.class, args); 123 } 124}