diff --git a/.gitignore b/.gitignore index ac7a7af1..bb2c3441 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ asset-pipeline-site/src/assets/html/apidoc/ .jruby-container .sass-cache .sass-work +.shelf/ diff --git a/asset-pipeline-core.ipr b/asset-pipeline-core.ipr index 2775d189..ea652b92 100644 --- a/asset-pipeline-core.ipr +++ b/asset-pipeline-core.ipr @@ -360,9 +360,12 @@ + - + + + @@ -1476,7 +1479,6 @@ - @@ -1567,7 +1569,6 @@ - @@ -1699,7 +1700,6 @@ - @@ -1709,7 +1709,6 @@ - @@ -2214,7 +2213,6 @@ - @@ -2925,7 +2923,6 @@ - diff --git a/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/NativeLibraryLoader.groovy b/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/NativeLibraryLoader.groovy new file mode 100644 index 00000000..5f7f9e1e --- /dev/null +++ b/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/NativeLibraryLoader.groovy @@ -0,0 +1,100 @@ +package asset.pipeline.dart + +import asset.pipeline.AssetPipelineConfigHolder +import com.caoccao.javet.enums.JSRuntimeType +import com.caoccao.javet.interop.loader.JavetLibLoader +import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j + +import java.nio.channels.Channels +import java.nio.channels.FileChannel +import java.nio.channels.ReadableByteChannel +import java.util.jar.JarEntry +import java.util.jar.JarFile + +import static com.caoccao.javet.utils.JavetOSUtils.* + +@Slf4j +@CompileStatic +class NativeLibraryLoader { + static final String JAVET_VERSION = NativeLibraryLoader.class.classLoader.getResource("javet-version.txt").openStream().text + static final String BASE_URL = AssetPipelineConfigHolder.config?.javetBaseUrl ?: "https://repo1.maven.org/maven2/com/caoccao/javet" + + final JavetLibLoader javetLibLoader + + NativeLibraryLoader(JSRuntimeType jsRuntimeType) { + javetLibLoader = new JavetLibLoader(jsRuntimeType) + } + + private URL getJavetJARUrl() { + new URL("${BASE_URL}/${javetPackageName}/${JAVET_VERSION}/${getJavetFileName()}") + } + + private String getJavetPackageName() { + IS_MACOS ? "javet-macos" : "javet" + } + + private String getJavetFileName() { + "${javetPackageName}-${JAVET_VERSION}.jar" + } + + /** + * The native libraries are packed in the Javet platform specific jars. First we need to download the correct JAR + * for this platform and then we will extract the native library from it and into a temporary directory for loading. + * + * @return the downloaded Javet jar file for this platform + */ + private File downloadJavetJar() { + File jarFile = new File(TEMP_DIRECTORY, javetFileName) + if (jarFile.exists()) { + log.debug("Jar file ${jarFile.path} exists, skipping download") + return jarFile + } + + // Download the file + URL jarUrl = getJavetJARUrl() + log.info("Downloading sass-dart native library integration: ${jarUrl}") + + copyToFile(jarUrl.openStream(), jarFile) + + log.debug("Downloaded $jarUrl to $jarFile (${jarFile.size()} bytes)") + jarFile + } + + private void copyToFile(InputStream inputStream, File targetFile) { + ReadableByteChannel readableByteChannel = Channels.newChannel(inputStream) + FileOutputStream fileOutputStream = new FileOutputStream(targetFile) + FileChannel fileChannel = fileOutputStream.getChannel() + fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE) + } + + /** + * A file reference to the platform and arch specific native library needed. + * @return the required native library for v8 integration + */ + File extractNativeLibrary() { + File file = downloadJavetJar() + if (!file.exists()) { + throw new IllegalStateException("Javet jar file $file does not exist, perhaps the download failed") + } + + String libraryName = javetLibLoader.libFileName + File jniLibrary = new File(TEMP_DIRECTORY, libraryName) + if (jniLibrary.exists()) { + log.debug("Native library $libraryName already exists, skipping extract") + return jniLibrary + } + + // Extract from the JAR file, should be in the root + JarFile jarFile = new JarFile(file) + JarEntry jarEntry = jarFile.getJarEntry(libraryName) + if (!jarEntry) { + throw new IllegalStateException("Could not load native library: $libraryName") + } + + copyToFile(jarFile.getInputStream(jarEntry), jniLibrary) + + log.debug("Extracted native library $libraryName to ${jniLibrary.path}") + jniLibrary + } +} diff --git a/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/NativeLibraryUtil.groovy b/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/NativeLibraryUtil.groovy deleted file mode 100644 index 2d762a31..00000000 --- a/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/NativeLibraryUtil.groovy +++ /dev/null @@ -1,106 +0,0 @@ -package asset.pipeline.dart - -import asset.pipeline.AssetPipelineConfigHolder -import com.caoccao.javet.enums.JSRuntimeType -import groovy.transform.CompileStatic -import groovy.util.logging.Slf4j - -import java.nio.channels.Channels -import java.nio.channels.FileChannel -import java.nio.channels.ReadableByteChannel -import java.nio.file.Files -import java.nio.file.Paths -import java.nio.file.StandardCopyOption -import java.util.jar.JarEntry -import java.util.jar.JarFile - -@Slf4j -@CompileStatic -class NativeLibraryUtil { - static final String JAVET_VERSION = NativeLibraryUtil.class.classLoader.getResource("javet-version.txt").openStream().text - static final String BASE_URL = AssetPipelineConfigHolder.config?.javetBaseUrl ?: "https://repo1.maven.org/maven2/com/caoccao/javet/javet-macos/${JAVET_VERSION}/" - - static final String TMP_DIR = System.getProperty("java.io.tmpdir") - static final String OS_ARCH = System.getProperty("os.arch") - static final String OS_NAME = System.getProperty("os.name") - - static final boolean IS_LINUX = OS_NAME.startsWith("Linux") - static final boolean IS_MACOS = OS_NAME.startsWith("Mac OS") - static final boolean IS_WINDOWS = OS_NAME.startsWith("Windows") - static final boolean IS_ARM64 = OS_ARCH.startsWith("arm64") || OS_ARCH.startsWith("armv8") || OS_ARCH == "aarch64" - static final boolean IS_X86_64 = OS_ARCH.matches(/^(x8664|amd64|ia32e|em64t|x64|x86_64)$/) - - static final String ARCH_NAME = IS_ARM64 ? 'arm64' : 'x86_64' - - static URL getJavetJARUrl() { - new URL(BASE_URL + getJavetFileName()) - } - - static String getJavetFileName() { - IS_MACOS ? "javet-macos-${JAVET_VERSION}.jar" : "javet-${JAVET_VERSION}.jar" - } - - static String getLibraryName(JSRuntimeType runtimeType) { - if (IS_MACOS) { - return "libjavet-${runtimeType.name}-macos-${ARCH_NAME}.v.${JAVET_VERSION}.dylib" - } - if (IS_LINUX) { - return "libjavet-${runtimeType.name}-linux-${ARCH_NAME}.v.${JAVET_VERSION}.so" - } - if (IS_WINDOWS) { - return "libjavet-${runtimeType.name}-windows-${ARCH_NAME}.v.${JAVET_VERSION}.dll" - } - - throw new IllegalStateException("Platform type not supported: ${OS_NAME} (${OS_ARCH}), expected Mac OS, Linux, or Windows") - } - - static File downloadJavetJar(JSRuntimeType runtimeType) { - File jarFile = new File(TMP_DIR, javetFileName) - if (jarFile.exists()) { - log.debug("Jar file ${jarFile.path} exists, skipping download") - return jarFile - } - - // Download the file - URL jarUrl = getJavetJARUrl() - log.info("Downloading sass-dart native library integration: ${jarUrl}") - - copyToFile(jarUrl.openStream(), jarFile) - - log.debug("Downloaded $jarUrl to $jarFile (${jarFile.size()} bytes)") - jarFile - } - - static File extractNativeLibrary(JSRuntimeType runtimeType) { - File file = downloadJavetJar(runtimeType) - if (!file.exists()) { - throw new IllegalStateException("Javet jar file $file does not exist, perhaps the download failed") - } - - String libraryName = getLibraryName(runtimeType) - File jniLibrary = new File(TMP_DIR, libraryName) - if (jniLibrary.exists()) { - log.debug("Native library $libraryName already exists, skipping extract") - return jniLibrary - } - - // Extract from the JAR file, should be in the root - JarFile jarFile = new JarFile(file) - JarEntry jarEntry = jarFile.getJarEntry(libraryName) - if (!jarEntry) { - throw new IllegalStateException("Could not load native library: $libraryName") - } - - copyToFile(jarFile.getInputStream(jarEntry), jniLibrary) - - log.debug("Extracted native library $libraryName to ${jniLibrary.path}") - jniLibrary - } - - static private void copyToFile(InputStream inputStream, File targetFile) { - ReadableByteChannel readableByteChannel = Channels.newChannel(inputStream) - FileOutputStream fileOutputStream = new FileOutputStream(targetFile) - FileChannel fileChannel = fileOutputStream.getChannel() - fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE) - } -} diff --git a/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/SassProcessor.groovy b/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/SassProcessor.groovy index 68170e1d..dce63c5f 100644 --- a/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/SassProcessor.groovy +++ b/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/SassProcessor.groovy @@ -33,7 +33,7 @@ class SassProcessor extends AbstractProcessor { final String sassCompiler static { - File nativeLibrary = NativeLibraryUtil.extractNativeLibrary(JSRuntimeType.Node) + File nativeLibrary = new NativeLibraryLoader(JSRuntimeType.Node).extractNativeLibrary() // Override Javet to use the library that we downloaded for this platform JavetLibLoader.setLibLoadingListener(new IJavetLibLoadingListener() { diff --git a/sass-dart-asset-pipeline/src/test/groovy/asset/pipeline/dart/SassProcessorSpec.groovy b/sass-dart-asset-pipeline/src/test/groovy/asset/pipeline/dart/SassProcessorSpec.groovy index c4f9b4e2..b1aaf67c 100644 --- a/sass-dart-asset-pipeline/src/test/groovy/asset/pipeline/dart/SassProcessorSpec.groovy +++ b/sass-dart-asset-pipeline/src/test/groovy/asset/pipeline/dart/SassProcessorSpec.groovy @@ -26,10 +26,13 @@ import spock.lang.Specification */ class SassProcessorSpec extends Specification { + void setup() { + AssetPipelineConfigHolder.config = [:] + } + void "should compile sass into css"() { given: AssetPipelineConfigHolder.resolvers = [] - AssetPipelineConfigHolder.config = [:] AssetPipelineConfigHolder.registerResolver(new FileSystemAssetResolver('test','assets')) def assetFile = AssetHelper.fileForFullName('test.scss') def processor = new SassProcessor() @@ -42,7 +45,6 @@ class SassProcessorSpec extends Specification { void "should compile nested sass into css"() { given: AssetPipelineConfigHolder.resolvers = [] - AssetPipelineConfigHolder.config = [:] AssetPipelineConfigHolder.registerResolver(new FileSystemAssetResolver('test','assets')) def assetFile = AssetHelper.fileForFullName('partials/forms.scss') def processor = new SassProcessor() @@ -55,7 +57,6 @@ class SassProcessorSpec extends Specification { void "should compile nested foo into css"() { given: AssetPipelineConfigHolder.resolvers = [] - AssetPipelineConfigHolder.config = [:] AssetPipelineConfigHolder.registerResolver(new FileSystemAssetResolver('test','assets')) def assetFile = AssetHelper.fileForFullName('foo/foo.scss') def processor = new SassProcessor() @@ -68,7 +69,6 @@ class SassProcessorSpec extends Specification { void "should compile Bourbon"() { given: AssetPipelineConfigHolder.resolvers = [] - AssetPipelineConfigHolder.config = [:] AssetPipelineConfigHolder.registerResolver(new FileSystemAssetResolver('test', 'assets')) def assetFile = AssetHelper.fileForFullName('bourbon-test.scss') def processor = new SassProcessor() @@ -81,7 +81,6 @@ class SassProcessorSpec extends Specification { void "should compile Bootstrap v4"() { given: AssetPipelineConfigHolder.resolvers = [] - AssetPipelineConfigHolder.config = [:] AssetPipelineConfigHolder.registerResolver(new FileSystemAssetResolver('test', 'assets')) def assetFile = AssetHelper.fileForFullName('bootstrap/bootstrap.scss') def processor = new SassProcessor()