001/* 002 * JDrupes Builder 003 * Copyright (C) 2025, 2026 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 com.google.common.flogger.FluentLogger; 022import java.net.MalformedURLException; 023import java.net.URI; 024import java.net.URL; 025import java.net.URLClassLoader; 026import java.nio.file.Path; 027import java.util.Arrays; 028import java.util.Collections; 029import java.util.Optional; 030import java.util.Properties; 031import org.apache.commons.cli.CommandLine; 032import org.apache.commons.cli.DefaultParser; 033import org.apache.commons.cli.ParseException; 034import org.jdrupes.builder.api.BuildException; 035import org.jdrupes.builder.api.FileResource; 036import org.jdrupes.builder.api.FileTree; 037import static org.jdrupes.builder.api.Intent.*; 038import org.jdrupes.builder.api.Launcher; 039import org.jdrupes.builder.api.RootProject; 040import org.jdrupes.builder.core.LauncherSupport; 041import org.jdrupes.builder.java.ClasspathElement; 042import org.jdrupes.builder.mvnrepo.MvnRepoLookup; 043 044/// A default implementation of a [Launcher]. The launcher first builds 045/// the user's JDrupes Builder project, using the JDrupes Builder project 046/// defined by [BootstrapRoot] and [BootstrapBuild]. The default action 047/// of [BootstrapRoot] adds the results from the bootstrap build 048/// to the classpath and launches the actual JDrupes Builder project. 049/// 050public class BootstrapLauncher extends AbstractLauncher { 051 052 private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 053 /// The JDrupes Builder properties read from the file 054 /// `.jdbld.properties` in the root project. 055 protected Properties jdbldProps; 056 /// The command line. 057 protected CommandLine commandLine; 058 private RootProject rootProject; 059 060 /// Initializes a new bootstrap launcher. 061 /// 062 public BootstrapLauncher() { 063 // Make javadoc happy. 064 } 065 066 /// Executes a build. An instance of the class passed as argument is 067 /// created and used as root project for the build. 068 /// 069 /// Unless the root project is the only project, the root project 070 /// must declare dependencies, else the subprojects won't be 071 /// instantiated. 072 /// 073 /// @param rootPrjCls the root project 074 /// @param args the args 075 /// 076 @SuppressWarnings("PMD.UseVarargs") 077 public void buildBuilderProject(Class<? extends RootProject> rootPrjCls, String[] args) { 078 unwrapBuildException(() -> { 079 Path buildRoot = Path.of("").toAbsolutePath(); 080 jdbldProps = propertiesFromFiles(buildRoot); 081 try { 082 commandLine = new DefaultParser().parse(baseOptions(), args); 083 } catch (ParseException e) { 084 throw new BuildException(e); 085 } 086 addCliProperties(jdbldProps, commandLine); 087 configureLogging(buildRoot, jdbldProps); 088 089 rootProject = LauncherSupport.createProjects(buildRoot, 090 rootPrjCls, Collections.emptyList(), jdbldProps, commandLine); 091 092 // Add build extensions to the build project. 093 var mvnLookup = new MvnRepoLookup(); 094 Optional.ofNullable(jdbldProps 095 .getProperty(BootstrapBuild.EXTENSIONS_SNAPSHOT_REPO, null)) 096 .map(URI::create).ifPresent(mvnLookup::snapshotRepository); 097 var buildCoords = Arrays.asList(jdbldProps 098 .getProperty(BootstrapBuild.BUILD_EXTENSIONS, "").split(",")) 099 .stream().map(String::trim).filter(c -> !c.isBlank()).toList(); 100 logger.atFine().log("Adding build extensions: %s" 101 + " to classpath for builder project compilation", buildCoords); 102 buildCoords.forEach(mvnLookup::resolve); 103 rootProject.project(BootstrapBuild.class).dependency(Expose, 104 mvnLookup); 105 var cpUrls = rootProject.resources(rootProject 106 .of(ClasspathElement.class).using(Supply, Expose)).map(cpe -> { 107 try { 108 if (cpe instanceof FileTree tree) { 109 return tree.root().toFile().toURI().toURL(); 110 } 111 return ((FileResource) cpe).path().toFile().toURI() 112 .toURL(); 113 } catch (MalformedURLException e) { 114 // Cannot happen 115 throw new BuildException(e); 116 } 117 }).toArray(URL[]::new); 118 logger.atFine().log("Launching build project with classpath: %s", 119 Arrays.toString(cpUrls)); 120 return new BuildLauncher( 121 new URLClassLoader(cpUrls, getClass().getClassLoader()), 122 buildRoot, args).runCommands(); 123 }); 124 } 125 126 /// Root project. 127 /// 128 /// @return the root project 129 /// 130 @Override 131 public RootProject rootProject() { 132 return rootProject; 133 } 134 135 /// The main method. 136 /// 137 /// @param args the arguments 138 /// 139 public static void main(String[] args) { 140 new BootstrapLauncher().buildBuilderProject(BootstrapRoot.class, args); 141 } 142}