lein - instalar clojure
¿"Mejores prácticas" para una biblioteca de clojure que usa librerías nativas? (3)
Liberé clj-nativedep a través de Clojars, que puede ayudar con este problema. La biblioteca proporciona la capacidad de identificar rápidamente un nombre normalizado para la arquitectura actual del sistema y puede cargar cualquier recurso seleccionado (dentro del jar o classpath) en el entorno de ejecución.
Consulte: https://github.com/rritoch/clj-nativedep
Este sistema se creó específicamente para mi proyecto WarpCTL, que utiliza una gran cantidad de código nativo generado con swig. Debido a la forma en que se maneja la carga de clases de Clojure, las dependencias nativas deben cargarse a través de un constructor de clase estático, puede ver un ejemplo en https://github.com/rritoch/WarpCTL/blob/master/extra/JADL-SDK/build/java/src/com/vnetpublishing/swig/adl/jadl_sdk.java#L13 . Para ese proyecto construyo el código java en un JAR y agrego clj-nativedep y el jar como una dependencia. Debería ser posible cargar recursos de esta manera desde aplicaciones de clojure puro, pero para obtener el mejor rendimiento debe cargarse desde un constructor de clase estática.
Si bien esto puede parecer subjetivo, hay un ejemplo concreto que me gustaría ayudar a resolver. Esto se relaciona con un problema con la biblioteca Overtone Clojure https://github.com/overtone/overtone/issues/274 que parece que debería haber una "mejor práctica" para Leiningen y se aplica a más bibliotecas que solo Overtone.
Overtone es una biblioteca de clojure que debe utilizarse dentro de otros proyectos. Overtone requiere que las bibliotecas nativas funcionen, por lo que usa :native-path "native"
en el project.clj https://github.com/overtone/overtone/blob/master/project.clj#L69 para obtener una ruta adecuada para las bibliotecas nativas de scsynth [overtone/scsynth "3.5.7.0"]
que se utilizan.
Sin embargo, creo que esto restablece la ruta de entrada de un proyecto que depende de la biblioteca de Overtone. Vea el problema para obtener algunos antecedentes, pero básicamente después de depender de [overtone "0.9.1"]
en un project.clj (System/getProperty "java.library.path")
devuelve solo la ruta nativa actual y el proyecto que usa Overtone no puede pasar en un camino a las bibliotecas locales.
Entonces, la pregunta es: ¿cómo puede un proyecto dependiente mezclar bibliotecas nativas locales con Overtone? ¿Debería Overtone o el proyecto dependiente ajustar sus configuraciones project.clj? ¿Cómo?
No sé de "mejor", pero aquí hay una práctica que me ha funcionado con éxito en al menos cuatro proyectos ahora, tres de los cuales eran "reales", comerciales. Aunque solo he abierto un caso específico para ZeroMQ , creo que los principios son genéricos y deberían funcionar para cualquier biblioteca nativa. La mayoría del código también podría ser fácilmente reutilizado, y está licenciado bajo Eclipse, así que siéntase libre de usarlo si lo desea. No he necesitado una versión más genérica de una biblioteca nativa, pero creo que podría extraerse fácilmente.
El problema que tuve con las soluciones estándar (lein: native-path, JVM args, etc.) fue que quería una solución portátil para la distribución de uberjar que no requiera que el usuario instale nada más , por lo que instrucciones como "descargue el uberjar , instale libzmq-dev desde su administrador de paquetes, y luego inicie uberjar "estaban fuera de cuestión.
El principio es bastante simple: agrupé las bibliotecas nativas dentro del contenedor de la biblioteca, para todas las plataformas compatibles. De esa manera:
- Yo, como autor de la biblioteca, controlo específicamente qué versiones se utilizan. No hay sorpresas allí.
- La inclusión de la biblioteca de otro proyecto se puede hacer sin ningún conocimiento de que la biblioteca se basa en las librerías nativas.
- Del mismo modo, la distribución de uberjar, ya sea de leiningen o maven, funciona perfectamente.
Tampoco estoy confiando en ninguna de las estrategias de vinculación del sistema operativo, ya que me resultó difícil hacer que funcionen de manera confiable. Entonces, lo que hice fue que incluí todas las bibliotecas nativas requeridas que no se garantiza que estén presentes en un sistema en ejecución dentro del contenedor, y luego, en tiempo de ejecución, la biblioteca:
- descubre dinámicamente en qué plataforma se está ejecutando;
- extrae las bibliotecas nativas de su propio archivo hacia el directorio temporal específico del sistema si aún no está allí (basado en un SHA; esto fue necesario para evitar la acumulación en plataformas que no limpian adecuadamente sus direcciones temporales, es decir, Windows) ;
- "manualmente" cargue las dependencias nativas en el orden correcto, por lo que la vinculación dinámica no intenta resolver las bibliotecas en la ruta del sistema operativo host.
Por supuesto, hay desventajas de ese enfoque:
- Como el proceso de compilación para un solo artefacto involucra tres sistemas operativos diferentes (yo apoyo Linux, Windows y OSX) y 2 arquitecturas (i386 y x86_64), el proceso de compilación es difícil de automatizar y requiere acceso a las máquinas relevantes;
- Debido a que se incluyen versiones específicas de las librerías nativas, los usuarios de la biblioteca no pueden actualizarlas; en entornos sensibles a la seguridad, esto puede ser inaceptable;
- Como usuario de esta biblioteca, debe confiar en mí que las bibliotecas nativas empaquetadas son realmente lo que afirmo que son, y de hecho se han construido a través del proceso que describo en el código fuente, sin más alteraciones;
- Como no sé prácticamente nada sobre la vinculación de bibliotecas dinámicas, puede haber otros problemas con este enfoque, aunque todavía no me han mordido.
También tuve este problema una vez, así que hice un plugin lein poco ortodoxo que resuelve eso de una vez por todas con un par de líneas agregadas a su project.clj
: https://bitbucket.org/noncom/nativot
ADVERTENCIA: es extremadamente inoportuno por la forma de Clojure ya que rompe todo el concepto de repetibilidad y cosas, lo que le permite incluir archivos, recursos y otros archivos arbitrarios en el archivo resultante y simplemente lo hace funcionar.