Skip to content

Commit

Permalink
Reduce shaded jar size by managing runtime dependencies using maven (f…
Browse files Browse the repository at this point in the history
…inos#123)

* Reduce shaded jar size by managing runtime dependencies using maven

* Incorporate review feedback: restrict catch block and explicitly set charset
  • Loading branch information
rafaelbey authored Jan 5, 2024
1 parent 2261539 commit fc8ae02
Show file tree
Hide file tree
Showing 17 changed files with 949 additions and 276 deletions.
4 changes: 0 additions & 4 deletions legend-engine-ide-lsp-default-extensions/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -595,10 +595,6 @@
<groupId>org.finos.legend.engine</groupId>
<artifactId>legend-engine-xt-relationalStore-pure</artifactId>
</dependency>
<dependency>
<groupId>org.finos.legend.engine</groupId>
<artifactId>legend-engine-xt-relationalStore-executionPlan-connection-api</artifactId>
</dependency>
<dependency>
<groupId>org.finos.legend.engine</groupId>
<artifactId>legend-engine-executionPlan-generation</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

package org.finos.legend.engine.ide.lsp.extension;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.factory.Sets;
Expand All @@ -27,12 +27,11 @@
import org.finos.legend.engine.language.pure.grammar.from.connection.ConnectionParser;
import org.finos.legend.engine.language.pure.grammar.from.extension.PureGrammarParserExtensionLoader;
import org.finos.legend.engine.language.pure.grammar.from.extension.PureGrammarParserExtensions;
import org.finos.legend.engine.plan.execution.stores.relational.connection.api.schema.model.DatabaseBuilderInput;
import org.finos.legend.engine.plan.execution.stores.relational.connection.api.schema.model.DatabasePattern;
import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.PackageableElement;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.connection.PackageableConnection;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.RelationalDatabaseConnection;
import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.Database;

/**
* Extension for the Connection grammar.
Expand Down Expand Up @@ -74,8 +73,8 @@ protected void collectCommands(SectionState sectionState, PackageableElement ele
public Iterable<? extends LegendExecutionResult> execute(SectionState section, String entityPath, String commandId, Map<String, String> executableArgs)
{
return GENERATE_DB_COMMAND_ID.equals(commandId) ?
generateDBFromConnection(section, entityPath) :
super.execute(section, entityPath, commandId, executableArgs);
generateDBFromConnection(section, entityPath) :
super.execute(section, entityPath, commandId, executableArgs);
}

private Iterable<? extends LegendExecutionResult> generateDBFromConnection(SectionState section, String entityPath)
Expand Down Expand Up @@ -111,7 +110,7 @@ private DatabaseBuilderInput buildInput(PackageableConnection packageableConn)
builderInput.config.enrichTables = true;
builderInput.config.enrichColumns = true;
builderInput.config.enrichPrimaryKeys = true;
builderInput.config.patterns = Lists.mutable.of(new DatabasePatternFixedTypo("%", "%", "%", false, false));
builderInput.config.patterns = Lists.mutable.of(new DatabasePattern());
builderInput.targetDatabase.name = packageableConn.name + "Database";
builderInput.targetDatabase._package = packageableConn._package;

Expand All @@ -125,19 +124,38 @@ private static ListIterable<String> findKeywords()
return Lists.immutable.withAll(keywords);
}

// todo remove once this is released - https://github.com/finos/legend-engine/pull/2507
private class DatabasePatternFixedTypo extends DatabasePattern
static class DatabaseBuilderInput
{
public DatabasePatternFixedTypo(String catalog, String schemaPattern, String tablePattern, boolean escapeSchemaPattern, boolean escapeTablePattern)
{
super(catalog, schemaPattern, tablePattern, escapeSchemaPattern, escapeTablePattern);
}
public DatabaseBuilderConfig config;

public RelationalDatabaseConnection connection;

public Database targetDatabase;

@Override
@JsonProperty("escapteTablePattern")
public boolean isEscapeTablePattern()
public DatabaseBuilderInput()
{
return super.isEscapeTablePattern();
this.config = new DatabaseBuilderConfig();
this.targetDatabase = new Database();
}
}

static class DatabasePattern
{
public final String catalog = "%";

public final String schemaPattern = "%";

public final String tablePattern = "%";
}

static class DatabaseBuilderConfig
{
public boolean enrichTables;

public boolean enrichPrimaryKeys;

public boolean enrichColumns;

public List<DatabasePattern> patterns = Lists.mutable.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import org.finos.legend.engine.ide.lsp.extension.diagnostic.LegendDiagnostic;
import org.finos.legend.engine.ide.lsp.extension.execution.LegendExecutionResult;
import org.finos.legend.engine.ide.lsp.extension.text.TextInterval;
import org.finos.legend.engine.plan.execution.stores.relational.connection.api.schema.model.DatabaseBuilderInput;
import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData;
import org.finos.legend.engine.shared.core.ObjectMapperFactory;
import org.junit.jupiter.api.Assertions;
Expand Down Expand Up @@ -94,7 +93,7 @@ void testGenerateDBFromConnectionCommand() throws IOException
ObjectMapper objectMapper = ObjectMapperFactory.getNewStandardObjectMapperWithPureProtocolExtensionSupports();
try
{
DatabaseBuilderInput body = objectMapper.readValue(exchange.getRequestBody(), DatabaseBuilderInput.class);
ConnectionLSPGrammarExtension.DatabaseBuilderInput body = objectMapper.readValue(exchange.getRequestBody(), ConnectionLSPGrammarExtension.DatabaseBuilderInput.class);
Assertions.assertEquals("model::MyStore", body.connection.element);
Assertions.assertEquals("model", body.targetDatabase._package);
Assertions.assertEquals("MyConnectionDatabase", body.targetDatabase.name);
Expand Down
20 changes: 19 additions & 1 deletion legend-engine-ide-lsp-server-shaded/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,14 @@
<bannedDependencies>
<includes>
<include>ch.qos.logback:*:${logback.version}:jar:runtime</include>
<include>com.google.code.gson:gson:*:jar:runtime</include>
<include>org.eclipse.lsp4j:*:*:jar:runtime</include>
<include>org.finos.legend.engine.ide.lsp:*:${project.version}:jar:runtime</include>
<include>org.slf4j:slf4j-api:${slf4j.version}:jar:runtime</include>
<!-- the below allowed dependencies are relocated on shaded jar to prevent conflicts when loading dynamically other jars-->
<include>com.google.code.gson:gson:*:jar:runtime</include>
<include>org.apache.maven.shared:maven-invoker:${maven.invoker.version}:jar:runtime</include>
<include>org.apache.maven.shared:maven-shared-utils:${maven.shared.utils.version}:jar:runtime</include>
<include>commons-io:commons-io:${commons-io.version}:jar:runtime</include>
</includes>
</bannedDependencies>
</rules>
Expand Down Expand Up @@ -78,6 +82,20 @@
<mainClass>org.finos.legend.engine.ide.lsp.server.LegendLanguageServer</mainClass>
</transformer>
</transformers>
<relocations>
<relocation>
<pattern>org.apache.maven.shared</pattern>
<shadedPattern>org.finos.legend.engine.ide.lsp.shaded.org.apache.maven.shared</shadedPattern>
</relocation>
<relocation>
<pattern>org.apache.commons.io</pattern>
<shadedPattern>org.finos.legend.engine.ide.lsp.shaded.org.apache.commons.io</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.gson</pattern>
<shadedPattern>org.finos.legend.engine.ide.lsp.shaded.com.google.gson</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
Expand Down
13 changes: 13 additions & 0 deletions legend-engine-ide-lsp-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
<include>org.eclipse.lsp4j:*:${lsp4j.version}:jar:compile</include>
<include>org.finos.legend.engine.ide.lsp:*:${project.version}:jar:compile</include>
<include>org.slf4j:slf4j-api:${slf4j.version}:jar:compile</include>
<include>org.apache.maven.shared:maven-invoker:${maven.invoker.version}:jar:compile</include>
<include>org.apache.maven.shared:maven-shared-utils:${maven.shared.utils.version}:jar:compile</include>
<include>commons-io:commons-io:${commons-io.version}:jar:compile</include>
</includes>
</bannedDependencies>
</rules>
Expand All @@ -64,6 +67,16 @@
</dependency>
<!-- LEGEND ENGINE LSP -->

<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-invoker</artifactId>
</dependency>

<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-shared-utils</artifactId>
</dependency>

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2024 Goldman Sachs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.finos.legend.engine.ide.lsp.classpath;

import java.util.concurrent.CompletableFuture;
import org.finos.legend.engine.ide.lsp.server.LegendLanguageServer;

public interface ClasspathFactory
{
CompletableFuture<ClassLoader> create(LegendLanguageServer server, Iterable<String> folders);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// Copyright 2024 Goldman Sachs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.finos.legend.engine.ide.lsp.classpath;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.apache.maven.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.DefaultInvoker;
import org.apache.maven.shared.invoker.InvocationRequest;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.Invoker;
import org.apache.maven.shared.invoker.InvokerLogger;
import org.apache.maven.shared.invoker.PrintStreamHandler;
import org.apache.maven.shared.invoker.PrintStreamLogger;
import org.apache.maven.shared.utils.Os;
import org.apache.maven.shared.utils.cli.CommandLineUtils;
import org.apache.maven.shared.utils.cli.Commandline;
import org.eclipse.lsp4j.ConfigurationItem;
import org.eclipse.lsp4j.ConfigurationParams;
import org.finos.legend.engine.ide.lsp.server.LegendLanguageServer;

public class ClasspathUsingMavenFactory implements ClasspathFactory
{
private final Invoker invoker;
private final File defaultPom;
private final ByteArrayOutputStream outputStream;

public ClasspathUsingMavenFactory(File defaultPom)
{
this.defaultPom = defaultPom;
this.invoker = new DefaultInvoker();
this.outputStream = new ByteArrayOutputStream();
this.invoker.setLogger(new PrintStreamLogger(new PrintStream(this.outputStream, true), InvokerLogger.INFO));
}

private static File getMavenExecLocation(String mavenHome) throws Exception
{
if (mavenHome == null || mavenHome.isEmpty())
{
Commandline commandline = new Commandline();

if (Os.isFamily(Os.FAMILY_WINDOWS))
{
commandline.setExecutable("where");
commandline.addArguments("mvn");
}
else if (Os.isFamily(Os.FAMILY_UNIX))
{
commandline.setExecutable("which");
commandline.addArguments("mvn");
}
else
{
throw new UnsupportedOperationException("OS not supported");
}

CommandLineUtils.StringStreamConsumer systemOut = new CommandLineUtils.StringStreamConsumer();
CommandLineUtils.StringStreamConsumer systemErr = new CommandLineUtils.StringStreamConsumer();
int result = CommandLineUtils.executeCommandLine(commandline, systemOut, systemErr, 2);

if (result == 0)
{
String[] split = systemOut.getOutput().split(System.lineSeparator());
if (split.length == 0)
{
return null;
}
String location = split[0];
return new File(location);
}
else
{
throw new RuntimeException("Error finding mvn executable: " + systemErr.getOutput());
}
}
else
{
return new File(mavenHome);
}
}

@Override
public CompletableFuture<ClassLoader> create(LegendLanguageServer server, Iterable<String> folders)
{
server.logInfoToClient("Discovering classpath using maven");

ConfigurationItem mavenExecPathConfig = new ConfigurationItem();
mavenExecPathConfig.setSection("maven.executable.path");

ConfigurationItem defaultPomConfig = new ConfigurationItem();
defaultPomConfig.setSection("legend.extensions.dependencies.pom");

ConfigurationParams configurationParams = new ConfigurationParams(Arrays.asList(mavenExecPathConfig, defaultPomConfig));
return server.getLanguageClient().configuration(configurationParams).thenApply(x ->
{
String mavenExecPath = server.extractValueAs(x.get(0), String.class);
String overrideDefaultPom = server.extractValueAs(x.get(1), String.class);

try
{
File maven = getMavenExecLocation(mavenExecPath);
server.logInfoToClient("Maven path: " + maven);

File pom = (overrideDefaultPom == null || overrideDefaultPom.isEmpty()) ? this.defaultPom : new File(overrideDefaultPom);

// todo apply properties from /project.json is this exists...
// todo if project.json exists, use pom from a sub-module
// todo otherwise, check if pom exists on root
// todo last, use a default pom...

server.logInfoToClient("Dependencies loaded from POM: " + pom);

File legendLspClasspath = File.createTempFile("legend_lsp_classpath", ".txt");
legendLspClasspath.deleteOnExit();

Properties properties = new Properties();
properties.setProperty("mdep.outputFile", legendLspClasspath.getAbsolutePath());

InvocationRequest request = new DefaultInvocationRequest();
request.setPomFile(pom);
request.setOutputHandler(new PrintStreamHandler(new PrintStream(this.outputStream, true), true));
request.setGoals(Collections.singletonList("dependency:build-classpath"));
request.setProperties(properties);
request.setTimeoutInSeconds((int) TimeUnit.MINUTES.toSeconds(5));
request.setJavaHome(Optional.ofNullable(System.getProperty("java.home")).map(File::new).orElse(null));
request.setMavenHome(maven);

InvocationResult result = this.invoker.execute(request);
if (result.getExitCode() != 0)
{
String output = this.outputStream.toString(StandardCharsets.UTF_8);
throw new IllegalStateException("Maven invoker failed\n\n" + output, result.getExecutionException());
}

String classpath = Files.readString(legendLspClasspath.toPath(), StandardCharsets.UTF_8);

server.logInfoToClient("Classpath used: " + classpath);

String[] classpathEntries = classpath.split(";");
URL[] urls = new URL[classpathEntries.length];

for (int i = 0; i < urls.length; i++)
{
urls[i] = new File(classpathEntries[i]).toURI().toURL();
}

ClassLoader parentClassloader = ClasspathUsingMavenFactory.class.getClassLoader();
return new URLClassLoader("legend-lsp", urls, parentClassloader);
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
finally
{
this.outputStream.reset();
}
});
}
}
Loading

0 comments on commit fc8ae02

Please sign in to comment.