Skip to content

Commit

Permalink
LIBRARY: dependency change now triggers recompilation
Browse files Browse the repository at this point in the history
Signed-off-by: imkiva <imkiva@islovely.icu>
  • Loading branch information
imkiva committed Nov 21, 2021
1 parent 50481e4 commit bbaff99
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 42 deletions.
92 changes: 59 additions & 33 deletions cli/src/main/java/org/aya/cli/library/LibraryCompiler.java
Expand Up @@ -28,6 +28,7 @@
import java.io.ObjectOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.function.Function;
import java.util.stream.IntStream;

Expand Down Expand Up @@ -60,28 +61,6 @@ public static int compile(@NotNull Path libraryRoot, boolean unicode) throws IOE
.collect(ImmutableSeq.factory());
}

private @NotNull DynamicLinkedSeq<Path> collectSource(@NotNull Path srcRoot) throws IOException {
return Files.walk(srcRoot).filter(Files::isRegularFile)
.filter(path -> path.getFileName().toString().endsWith(".aya"))
.map(ResolveInfo::canonicalize)
.collect(DynamicLinkedSeq.factory());
}

private void collectDep(@NotNull MutableGraph<ResolveInfo> dep, @NotNull ResolveInfo info) {
dep.suc(info).appendAll(info.imports());
info.imports().forEach(i -> collectDep(dep, i));
}

private void collectChanged(
@NotNull MutableGraph<ResolveInfo> usage,
@NotNull ResolveInfo changed,
@NotNull MutableSet<ResolveInfo> changedList
) {
if (changedList.contains(changed)) return;
changedList.add(changed);
usage.suc(changed).forEach(dep -> collectChanged(usage, dep, changedList));
}

private @NotNull ResolveInfo resolveModule(
@NotNull ModuleLoader moduleLoader,
@NotNull SourceFileLocator locator,
Expand Down Expand Up @@ -117,37 +96,50 @@ private void collectChanged(
return graph;
}

private void make(@NotNull LibraryConfig config) throws IOException {
/**
* Incrementally compiles a library.
*
* @return whether the library is up-to-date.
* @apiNote The return value does not indicate whether the library is compiled successfully.
*/
private boolean make(@NotNull LibraryConfig config) throws IOException {
// TODO[kiva]: move to package manager
var modulePath = DynamicSeq.<Path>create();
var thisModulePath = DynamicSeq.<Path>create();
var locatorPath = DynamicSeq.<Path>create();
var anyDepChanged = false;
for (var dep : config.deps()) {
var depConfig = depConfig(dep);
if (depConfig == null) {
System.out.println("Skipping " + dep.depName());
continue;
}
make(depConfig);
modulePath.append(depConfig.libraryOutRoot());
var upToDate = make(depConfig);
anyDepChanged = anyDepChanged || !upToDate;
thisModulePath.append(depConfig.libraryOutRoot());
locatorPath.append(depConfig.librarySrcRoot());
}

System.out.println("Compiling " + config.name());
// TODO: be incremental when dependencies changed
if (anyDepChanged) deleteRecursively(config.libraryOutRoot());

var srcRoot = config.librarySrcRoot();
var outRoot = Files.createDirectories(config.libraryOutRoot());
modulePath.append(srcRoot);
var thisOutRoot = Files.createDirectories(config.libraryOutRoot());
thisModulePath.append(srcRoot);
locatorPath.prepend(srcRoot);
var locator = new SourceFileLocator.Module(locatorPath.view());

var reporter = CliReporter.stdio(unicode);
var timestamp = new Timestamp(locator, outRoot);
var loader = new CachedModuleLoader(new LibraryModuleLoader(locator, timestamp, modulePath.view(), outRoot, reporter));
var timestamp = new Timestamp(locator, thisOutRoot);
var loader = new CachedModuleLoader(new LibraryModuleLoader(reporter, locator, timestamp, thisModulePath.view(), thisOutRoot));
var depGraph = resolveLibrary(loader, locator, config);
recompile(reporter, depGraph, timestamp);
return make(reporter, depGraph, timestamp);
}

private void recompile(
/**
* @return whether the library is up-to-date.
*/
private boolean make(
@NotNull Reporter reporter,
@NotNull MutableGraph<ResolveInfo> depGraph,
@NotNull Timestamp timestamp
Expand All @@ -161,7 +153,7 @@ private void recompile(

if (changed.isEmpty()) {
System.out.println(" [Info] No changes detected, no need to remake");
return;
return true;
}

var incrementalDep = MutableGraph.<ResolveInfo>create();
Expand All @@ -181,6 +173,7 @@ private void recompile(
},
null);
});
return false;
}

private static @NotNull Path coreFile(
Expand Down Expand Up @@ -231,4 +224,37 @@ private void saveCompiledCore(@NotNull ResolveInfo resolveInfo, @NotNull Immutab
coreFile(timestamp.locator, sourcePath, timestamp.outRoot)));
}
}

private static @NotNull DynamicLinkedSeq<Path> collectSource(@NotNull Path srcRoot) throws IOException {
try (var walk = Files.walk(srcRoot)) {
return walk.filter(Files::isRegularFile)
.filter(path -> path.getFileName().toString().endsWith(".aya"))
.map(ResolveInfo::canonicalize)
.collect(DynamicLinkedSeq.factory());
}
}

private static void collectDep(@NotNull MutableGraph<ResolveInfo> dep, @NotNull ResolveInfo info) {
dep.suc(info).appendAll(info.imports());
info.imports().forEach(i -> collectDep(dep, i));
}

private static void collectChanged(
@NotNull MutableGraph<ResolveInfo> usage,
@NotNull ResolveInfo changed,
@NotNull MutableSet<ResolveInfo> changedList
) {
if (changedList.contains(changed)) return;
changedList.add(changed);
usage.suc(changed).forEach(dep -> collectChanged(usage, dep, changedList));
}

private static void deleteRecursively(@NotNull Path path) throws IOException {
if (!Files.exists(path)) return;
try (var walk = Files.walk(path)) {
walk.sorted(Comparator.reverseOrder())
.collect(ImmutableSeq.factory())
.forEachChecked(Files::deleteIfExists);
}
}
}
50 changes: 41 additions & 9 deletions cli/src/main/java/org/aya/cli/library/LibraryModuleLoader.java
Expand Up @@ -21,38 +21,70 @@
import java.nio.file.Path;

/**
* Same as {@link FileModuleLoader}, but only resolves the module.
* This module loader is used to load source/compiled modules in a library.
* For modules belonging to this library, both compiled/source modules are searched and loaded (compiled first).
* For modules belonging to dependencies, only compiled modules are searched and loaded.
* This is archived by not storing source dirs of dependencies to
* {@link LibraryModuleLoader#thisModulePath} and always trying to find compiled cores in
* {@link LibraryModuleLoader#thisOutRoot}.
*
* <p>
* Unlike {@link FileModuleLoader}, this loader only resolves the module rather than tycking it.
* Tyck decision is made by {@link LibraryCompiler} by judging whether the source file
* is modified since last build.
*
* @param thisModulePath Source dirs of this module, out dirs of all dependencies.
* @param thisOutRoot Out dir of this module.
* @author kiva
* @see FileModuleLoader
*/
public record LibraryModuleLoader(
@NotNull Reporter reporter,
@NotNull SourceFileLocator locator,
@NotNull LibraryCompiler.Timestamp timestamp,
@NotNull SeqView<Path> modulePath,
@NotNull Path outRoot,
@NotNull Reporter reporter
@NotNull SeqView<Path> thisModulePath,
@NotNull Path thisOutRoot
) implements ModuleLoader {
static @NotNull Path resolveCompiledCore(@NotNull Path basePath, @NotNull Seq<@NotNull String> moduleName) {
var withoutExt = moduleName.foldLeft(basePath, Path::resolve);
return withoutExt.resolveSibling(withoutExt.getFileName() + ".ayac");
}

static @NotNull Path resolveFile(@NotNull SeqView<Path> modulePath, @NotNull Seq<String> moduleName) {
static @Nullable Path resolveCompiledDepCore(@NotNull SeqView<Path> modulePath, @NotNull Seq<String> moduleName) {
for (var p : modulePath) {
var file = resolveCompiledCore(p, moduleName);
if (Files.exists(file)) return file;
}
return null;
}

static @Nullable Path resolveFile(@NotNull SeqView<Path> modulePath, @NotNull Seq<String> moduleName) {
for (var p : modulePath) {
var file = FileModuleLoader.resolveFile(p, moduleName);
if (Files.exists(file)) return file;
}
throw new IllegalArgumentException("invalid module path");
return null;
}

@Override
public @Nullable ResolveInfo load(@NotNull ImmutableSeq<@NotNull String> mod, @NotNull ModuleLoader recurseLoader) {
var sourcePath = resolveFile(modulePath, mod);
var corePath = resolveCompiledCore(outRoot, mod);
if (Files.exists(corePath) && !timestamp().sourceModified(sourcePath))
var sourcePath = resolveFile(thisModulePath, mod);
if (sourcePath == null) {
// We are loading a module belonging to dependencies, find the compiled core.
// The compiled core should always exist, otherwise the dependency is not built.
var depCorePath = resolveCompiledDepCore(thisModulePath, mod);
assert depCorePath != null : "dependencies not built?";
return loadCompiledCore(mod, depCorePath, depCorePath);
}

// we are loading a module belonging to this library, try finding compiled core first.
// If found, check modifications and decide whether to proceed with compiled core.
var corePath = resolveCompiledCore(thisOutRoot, mod);
if (Files.exists(corePath) && !timestamp().sourceModified(sourcePath)) {
return loadCompiledCore(mod, corePath, sourcePath);
}

// No compiled core is found, or source file is modified, compile it from source.
try {
var program = AyaParsing.program(locator, reporter, sourcePath);
var context = new EmptyContext(reporter, sourcePath).derive(mod);
Expand Down

0 comments on commit bbaff99

Please sign in to comment.