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}