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}