Skip to content

Commit

Permalink
Native Build work
Browse files Browse the repository at this point in the history
  • Loading branch information
melloware committed Oct 26, 2024
1 parent 262c359 commit baadb02
Show file tree
Hide file tree
Showing 6 changed files with 296 additions and 6 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,5 @@ jobs:
- name: Build with Maven
run: mvn -B clean install -Dno-format --no-transfer-progress

#- name: Build with Maven (Native)
# run: mvn -B install -Dnative -Dquarkus.native.container-build -Dnative.surefire.skip --no-transfer-progress
- name: Build with Maven (Native)
run: mvn -B install -Dnative -Dquarkus.native.container-build -Dnative.surefire.skip --no-transfer-progress
Original file line number Diff line number Diff line change
@@ -1,7 +1,36 @@
package io.quarkiverse.playwright.deployment;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;

import com.microsoft.playwright.Browser;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.impl.driver.jar.DriverJar;
import com.microsoft.playwright.options.HttpHeader;
import com.microsoft.playwright.options.Timing;
import com.microsoft.playwright.options.ViewportSize;

import io.quarkiverse.playwright.runtime.PlaywrightRecorder;
import io.quarkus.deployment.IsNormal;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.IndexDependencyBuildItem;
import io.quarkus.deployment.builditem.NativeImageEnableAllCharsetsBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourcePatternsBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.logging.Log;

class PlaywrightProcessor {

Expand All @@ -11,4 +40,133 @@ class PlaywrightProcessor {
FeatureBuildItem feature() {
return new FeatureBuildItem(FEATURE);
}

@BuildStep
void indexTransitiveDependencies(BuildProducer<IndexDependencyBuildItem> index) {
index.produce(new IndexDependencyBuildItem("com.microsoft.playwright", "playwright"));
index.produce(new IndexDependencyBuildItem("com.microsoft.playwright", "driver-bundle"));
index.produce(new IndexDependencyBuildItem("com.microsoft.playwright", "driver"));
}

@BuildStep
NativeImageEnableAllCharsetsBuildItem enableAllCharsetsBuildItem() {
return new NativeImageEnableAllCharsetsBuildItem();
}

@BuildStep
void registerForReflection(CombinedIndexBuildItem combinedIndex, BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
//@formatter:off
final List<String> classNames = new ArrayList<>();

classNames.add("com.microsoft.playwright.impl.Message");
classNames.add("com.microsoft.playwright.impl.SerializedArgument");
classNames.add("com.microsoft.playwright.impl.SerializedValue");
classNames.add("com.microsoft.playwright.impl.SerializedValue$O");
classNames.add(Browser.CloseOptions.class.getName());
classNames.add(Browser.NewContextOptions.class.getName());
classNames.add(Browser.NewPageOptions.class.getName());
classNames.add(Browser.StartTracingOptions.class.getName());
classNames.add(DriverJar.class.getName());
classNames.add(ElementHandle.CheckOptions.class.getName());
classNames.add(ElementHandle.ClickOptions.class.getName());
classNames.add(ElementHandle.DblclickOptions.class.getName());
classNames.add(ElementHandle.FillOptions.class.getName());
classNames.add(ElementHandle.HoverOptions.class.getName());
classNames.add(ElementHandle.InputValueOptions.class.getName());
classNames.add(ElementHandle.PressOptions.class.getName());
classNames.add(ElementHandle.ScreenshotOptions.class.getName());
classNames.add(ElementHandle.ScrollIntoViewIfNeededOptions.class.getName());
classNames.add(ElementHandle.SelectTextOptions.class.getName());
classNames.add(ElementHandle.SetInputFilesOptions.class.getName());
classNames.add(ElementHandle.TapOptions.class.getName());
classNames.add(ElementHandle.TypeOptions.class.getName());
classNames.add(ElementHandle.UncheckOptions.class.getName());
classNames.add(ElementHandle.WaitForElementStateOptions.class.getName());
classNames.add(ElementHandle.WaitForSelectorOptions.class.getName());
classNames.add(HttpHeader.class.getName());
classNames.add(Timing.class.getName());
classNames.add(ViewportSize.class.getName());
classNames.addAll(collectImplementors(combinedIndex, Playwright.class.getName()));

//@formatter:on
final TreeSet<String> uniqueClasses = new TreeSet<>(classNames);
Log.debugf("Playwright Reflection: %s", uniqueClasses);

reflectiveClass.produce(
ReflectiveClassBuildItem.builder(uniqueClasses.toArray(new String[0])).constructors().methods().fields()
.serialization().unsafeAllocated().build());
}

@BuildStep(onlyIf = IsNormal.class)
@Record(ExecutionTime.RUNTIME_INIT)
void bindDrivers(PlaywrightRecorder recorder) {
recorder.initialize();
}

@BuildStep
void registerDrivers(BuildProducer<NativeImageResourcePatternsBuildItem> nativeImageResourcePatterns) {
final NativeImageResourcePatternsBuildItem.Builder builder = NativeImageResourcePatternsBuildItem.builder();
builder.includeGlob("driver/**");
nativeImageResourcePatterns.produce(builder.build());
}

protected List<String> collectClassesInPackage(CombinedIndexBuildItem combinedIndex, String packageName) {
final List<String> classes = new ArrayList<>();
final List<DotName> packages = new ArrayList<>(combinedIndex.getIndex().getSubpackages(packageName));
packages.add(DotName.createSimple(packageName));
for (DotName aPackage : packages) {
final List<String> packageClasses = combinedIndex.getIndex()
.getClassesInPackage(aPackage)
.stream()
.map(ClassInfo::toString)
.toList();
classes.addAll(packageClasses);
}
Log.debugf("Package Classes: %s", classes);
return classes;
}

protected List<String> collectInterfacesInPackage(CombinedIndexBuildItem combinedIndex, String packageName) {
final List<String> classes = new ArrayList<>();
final List<DotName> packages = new ArrayList<>(combinedIndex.getIndex().getSubpackages(packageName));
packages.add(DotName.createSimple(packageName));
for (DotName aPackage : packages) {
final List<String> packageClasses = combinedIndex.getIndex()
.getClassesInPackage(aPackage)
.stream()
.filter(ClassInfo::isInterface) // Filter only interfaces
.map(ClassInfo::toString)
.toList();
classes.addAll(packageClasses);
}
Log.debugf("Package Interfaces: %s", classes);
return classes;
}

protected List<String> collectSubclasses(CombinedIndexBuildItem combinedIndex, String className) {
List<String> classes = combinedIndex.getIndex()
.getAllKnownSubclasses(DotName.createSimple(className))
.stream()
.map(ClassInfo::toString)
.collect(Collectors.toList());
classes.add(className);
Log.debugf("Subclasses: %s", classes);
return classes;
}

protected List<String> collectImplementors(CombinedIndexBuildItem combinedIndex, String className) {
Set<String> classes = combinedIndex.getIndex()
.getAllKnownImplementors(DotName.createSimple(className))
.stream()
.map(ClassInfo::toString)
.collect(Collectors.toCollection(HashSet::new));
classes.add(className);
Set<String> subclasses = new HashSet<>();
for (String implementationClass : classes) {
subclasses.addAll(collectSubclasses(combinedIndex, implementationClass));
}
classes.addAll(subclasses);
Log.debugf("Implementors: %s", classes);
return new ArrayList<>(classes);
}
}
58 changes: 57 additions & 1 deletion integration-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,21 @@
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>

</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<executable>docker</executable>
<workingDirectory>${project.basedir}</workingDirectory>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
Expand Down Expand Up @@ -122,5 +134,49 @@
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
<profile>
<id>native-docker</id>
<activation>
<property>
<name>native-docker</name>
</property>
</activation>
<properties>
<skipITs>true</skipITs>
<quarkus.native.enabled>true</quarkus.native.enabled>
<quarkus.native.additional-build-args>
--trace-object-instantiation=java.awt.BasicStroke
</quarkus.native.additional-build-args>
</properties>
<build>
<defaultGoal>clean package</defaultGoal>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<!-- $ docker build -f src/main/docker/Dockerfile.native -t playwright/integration-test:version . -->
<execution>
<id>docker-build</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<arguments>
<argument>build</argument>
<argument>-f</argument>
<argument>src/main/docker/Dockerfile.native</argument>
<argument>-t</argument>
<argument>playwright/integration-test:${project.version}</argument>
<argument>.</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
36 changes: 36 additions & 0 deletions integration-tests/src/main/docker/Dockerfile.native
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
#
# Before building the container image run:
#
# ./mvnw package -Pnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native -t playwright/integration-test .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 playwright/integration-test
#
###
FROM mcr.microsoft.com/playwright:v1.48.1-noble

WORKDIR /work/

# Set permissions for /work directory and grant full access to /tmp
RUN chown 1001:root /work \
&& chmod g+rwX /work \
&& chown 1001:root /work

# Copy necessary files and adjust permissions
COPY --chown=1001:root target/*.properties target/*.so /work/
COPY --chown=1001:root target/*-runner /work/application

# Make application executable for all users
RUN chmod ugo+x /work/application

EXPOSE 8080
USER 1001

CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.jboss.logging.Logger;

import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Page;
Expand All @@ -32,24 +34,27 @@
@Path("/playwright")
@ApplicationScoped
public class PlaywrightResource {
// add some rest methods here

private static final Logger log = Logger.getLogger(PlaywrightResource.class);

@GET
public String google() {
String pageTitle = "Hello playwright";
String pageTitle;
final BrowserType.LaunchOptions launchOptions = new BrowserType.LaunchOptions()
.setHeadless(true)
.setChromiumSandbox(false)
.setChannel("")
.setArgs(List.of("--disable-gpu"));
final Map<String, String> env = new HashMap<>(System.getenv());
env.put("DEBUG", "pw:api");
try (Playwright playwright = Playwright.create(new Playwright.CreateOptions().setEnv(env))) {
try (Browser browser = playwright.chromium().launch(launchOptions)) {
Page page = browser.newPage();
page.navigate("https://www.google.com/");
pageTitle = page.title();
log.infof("Page title: %s", pageTitle);
}
}
return pageTitle;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.quarkiverse.playwright.runtime;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.util.Collections;

import org.jboss.logging.Logger;

import com.microsoft.playwright.impl.driver.jar.DriverJar;

import io.quarkus.runtime.annotations.Recorder;

@Recorder
public class PlaywrightRecorder {

private static final Logger log = Logger.getLogger(PlaywrightRecorder.class);

public void initialize() {
try {
URI uri = DriverJar.getDriverResourceURI();
log.infof("Playwright Driver: %s", uri);
FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap());
if (fs == null) {
log.errorf("FileSystem Error NULL: %s", uri);
}
DriverJar jar = new DriverJar();
log.debugf("Playwright Driver Directory: %s", jar.driverDir());
} catch (URISyntaxException | IOException e) {
throw new RuntimeException(e);
}
}
}

0 comments on commit baadb02

Please sign in to comment.