package building.Makeshift; // Changes to this file immediately affect the next build. Treat it as a build script. import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Predicate; import static building.Makeshift.Bootstrap.addCompilableSource; import static building.Makeshift.Bootstrap.pathTester_true; import static building.Makeshift.Bootstrap.toPackageName; import static building.Makeshift.Bootstrap.toProperPath; import static building.Makeshift.Bootstrap.typeName; import static building.Makeshift.Bootstrap.verify; import static building.Makeshift.Bootstrap.Unhandled; import static building.Makeshift.Bootstrap.UserError; import static java.nio.file.Files.isRegularFile; /** A builder of software builders. In place of the {@linkplain BuilderBuilderDefault default}, * a project may define its own builder by putting a source file named `BuilderBuilder.java` into its * {@linkplain #internalBuildingCode(Path) building code}. The class definition must be public and must * include a public constructor that takes no parameters. It must inherit from the present interface. * It must depend on no code outside of: */// Changing the above? Sync → stage 1 of `run` in `bin/build`. public interface BuilderBuilder { /** Packages of building code additional to * the {@linkplain #internalBuildingCode() internal building code}. The added code comprises * all `.java` files of the {@linkplain Bootstrap#toProperPath(String) equivalent directories}, * exclusive of their subdirectories. Such code may be intended for the use of other projects, for * example, as part of their {@linkplain #externalBuildingCode() external building code}. * *

The default implementation is an empty set.

* * @see #internalBuildingCode(Path) */ public default Set addedBuildingCode() { return Set.of(); } /* Packages for elements because they are codeable by implementers as cross-platform literals, whereas paths are not. */ /** Compiles the code of the software builder, including any * {@linkplain #externalBuildingCode() external building code} on which it depends, * and prepares it for use. * *

To get an instance of the builder once built, use {@linkplain #newBuilder() newBuilder}.

* * @throws IllegalStateException If already a build had started. */ public default void build() throws UserError { final String owningProject = projectPackage(); if( projectsUnderBuild.contains( owningProject )) throw new IllegalStateException(); projectsUnderBuild.add( owningProject ); // Build the external building code // ──────────────────────────────── for( final String externalProject: externalBuildingCode() ) { /* Iteration order is unimportant; regardless projects will build here in correct order. Makeshift, for instance, will always build before any other project that nominally depends on it. */ if( projectsUnderBuild.contains( externalProject )) continue; forPackage(externalProject).build(); } // Compile the project’s own building code // ─────────────────────────────────────── final List sourceNames = new ArrayList<>(); final Predicate tester = targetFile().getFileName().toString().equals( "Target.java" ) ? pathTester_true : p -> p.getFileName().toString().startsWith("Build"); addCompilableSource( sourceNames, internalBuildingCode(projectPath()), tester ); addedBuildingCode().forEach( pkg -> addCompilableSource( sourceNames, toProperPath(pkg) )); if( sourceNames.size() > 0 ) Bootstrap.compile( owningProject, sourceNames ); } /** The proper package of each project, less the {@linkplain #projectPackage() owning project}, * whose {@linkplain #internalBuildingCode() building code} the software builder may depend on. * The default implementation is a singleton set comprising ‘building.Makeshift’. * * @see #internalBuildingCode(Path) */ public default Set externalBuildingCode() { return Set.of( "building.Makeshift" ); } /** Gives a builder builder for a project, first compiling its code if necessary. * * @param projectPackage The proper package of the project. */ public static BuilderBuilder forPackage( final String projectPackage ) throws UserError { verify( projectPackage ); return get( projectPackage, /*projectPath*/toProperPath( projectPackage )); } /** Gives a builder builder for a project, first compiling its code if necessary. * * @param projectPath The proper path of the project. */ public static BuilderBuilder forPath( final Path projectPath ) throws UserError { verify( projectPath ); return get( /*projectPackage*/toPackageName(projectPath), projectPath ); } /** Gives the proper path of a project’s builder-builder source file. The given path is either * `{@linkplain #internalBuildingCode(Path) internalBuildingCode}/BuilderBuilder.java` if a * file exists there, or the path to the {@linkplain BuilderBuilderDefault default implementation}. * * @param projectPath The proper path of the project. */ public static Path implementationFile( final Path projectPath ) { // Cf. @ `Builder`. verify( projectPath ); Path p = internalBuildingCode(projectPath).resolve( "BuilderBuilder.java" ); if( !isRegularFile( p )) p = implementationFileDefault; return p; } /** The proper path of the source file for the * {@linkplain BuilderBuilderDefault default implementation}. */ public static final Path implementationFileDefault = Bootstrap.projectPath.resolve( "BuilderBuilderDefault.java" ); /** Gives the proper path of the directory containing a project’s internal building code. * Gives either (a) `projectPath/builder/` if a directory exists there, * else (b) `projectPath/`. A project may store its internal building code * in this directory alone, exclusive of subdirectories. * *

Moreover, in the default implementation, if (c) this directory contains a file * named `BuildTarget.java`, then it defines the build targets of the project * and the building code comprises only those files whose names begin with `Build`. * Otherwise the building code comprises all child files of the directory.

* * @param projectPath The proper path of the project. * @see #addedBuildingCode() * @see #externalBuildingCode() * @see Example of (a) * @see Example of (b) * @see Example of (c) */ public static Path internalBuildingCode( final Path projectPath ) { verify( projectPath ); Path p = projectPath.resolve( "builder" ); if( !Files.isDirectory( p )) p = projectPath; return p; } /** Makes an instance of the software builder, once {@linkplain #build() built}. */ public default Builder newBuilder() { try { final Class cBuilder = Class.forName( typeName( Builder.implementationFile( projectPath() ))) .asSubclass( Builder.class ); final Class cTarget = Class.forName( typeName( targetFile() )).asSubclass( Enum.class ); try { // Either (a) the default implementation `BuilderDefault`, or (b) a custom one: return cBuilder.getConstructor( Class.class, String.class, Path.class ) // (a) .newInstance( cTarget, projectPackage(), projectPath() ); } catch( NoSuchMethodException x ) { return cBuilder.getConstructor().newInstance(); }} // (b) catch( ReflectiveOperationException x ) { throw new Unhandled( x ); }} /** The proper package of the owning project. */ public String projectPackage(); /** The proper path of the owning project. */ public Path projectPath(); /** Projects for which a {@linkplain #build() builder build} was called in the present runtime, * each identified by its proper package. */ public static final Set projectsUnderBuild = new HashSet<>(); /** The proper path of the source file defining the build targets. The default implementation * is a child path of the {@linkplain #internalBuildingCode(Path) internal building code} * with a simple name of either `BuildTarget.java` if a file exists there, else `Target.java`. */ public default Path targetFile() { final Path iBC = internalBuildingCode( projectPath() ); Path p = iBC.resolve( "BuildTarget.java" ); if( !isRegularFile( p )) p = iBC.resolve( "Target.java" ); return p; } //// P r i v a t e //////////////////////////////////////////////////////////////////////////////////// /** Gives a builder builder for a project, first compiling its code if necessary. * * @param projectPackage The proper package of the project. * @param projectPath The proper path of the project. */ private static BuilderBuilder get( final String projectPackage, final Path projectPath ) throws UserError { // Compile the code // ──────────────── final Path iFile = implementationFile( projectPath ); final Path iDirectory = iFile.getParent(); final String iSimpleTypeName = Bootstrap.simpleTypeName( iFile ); if( Bootstrap.toCompile( iFile, iSimpleTypeName )) { Bootstrap.compile( null/*builder builder*/, List.of( iFile.toString() )); } // Construct an instance // ───────────────────── final String cName = toPackageName(iDirectory) + '.' + iSimpleTypeName; try { final Class c = Class.forName( cName ) .asSubclass( BuilderBuilder.class ); try { // Either (a) the default implementation `BuilderBuilderDefault`, or (b) a custom one: return c.getConstructor( String.class, Path.class ) // (a) .newInstance( projectPackage, projectPath ); } catch( NoSuchMethodException x ) { return c.getConstructor().newInstance(); }} // (b) catch( ReflectiveOperationException x ) { throw new Unhandled( x ); }}} // Copyright © 2020-2022 Michael Allan. Licence MIT.