Maven
Maven یک ابزار ساخت شناختهشده برای بومسازگان Java است؛ با این حال، هنگام یکپارچهسازی در سیستم ساخت Nix با چالشهایی همراه است.
موارد زیر فهرستی از الگوهای رایج نحوه بستهبندی یک پروژه Maven (یا هر زبان JVM که امکان خروجی گرفتن برای Maven را دارد) به عنوان یک بسته Nix ارائه میدهد.
ساخت یک بسته با استفاده از maven.buildMavenPackage
بسته زیر را در نظر بگیرید:
{
lib,
fetchFromGitHub,
jre,
makeWrapper,
maven,
}:
maven.buildMavenPackage (finalAttrs: {
pname = "jd-cli";
version = "1.2.1";
src = fetchFromGitHub {
owner = "intoolswetrust";
repo = "jd-cli";
tag = "jd-cli-${finalAttrs.version}";
hash = "sha256-rRttA5H0A0c44loBzbKH7Waoted3IsOgxGCD2VM0U/Q=";
};
mvnHash = "sha256-kLpjMj05uC94/5vGMwMlFzLKNFOKeyNvq/vmB6pHTAo=";
nativeBuildInputs = [ makeWrapper ];
installPhase = ''
runHook preInstall
mkdir -p $out/bin $out/share/jd-cli
install -Dm644 jd-cli/target/jd-cli.jar $out/share/jd-cli
makeWrapper ${jre}/bin/java $out/bin/jd-cli \
--add-flags "-jar $out/share/jd-cli/jd-cli.jar"
runHook postInstall
'';
meta = {
description = "Simple command line wrapper around JD Core Java Decompiler project";
homepage = "https://github.com/intoolswetrust/jd-cli";
license = lib.licenses.gpl3Plus;
maintainers = with lib.maintainers; [ majiir ];
};
}) مراجعه کنید.
Wait, "را ببینید" or "مراجعه کنید" for "see"? In Nix docs, "را ببینید" or "مراجعه کنید" works well. Let
overrideMavenAttrs :: (AttrSet -> Derivation) | ((AttrSet -> Attrset) -> Derivation) -> Derivation خروجی buildMavenPackage دارای صفت (attribute) overrideMavenAttrs است، که تابعی است که یکی از موارد زیر را میپذیرد:
- هر زیرمجموعهای از صفات که
jd-cli.overrideMavenAttrs (old: rec {
version = "1.2.0";
src = fetchFromGitHub {
owner = old.src.owner;
repo = old.src.repo;
tag = "${old.pname}-${version}";
# old source hash of 1.2.0 version
hash = "sha256-US7j6tQ6mh1libeHnQdFxPGoxHzbZHqehWSgCYynKx8=";
};
# tests can be disabled by prefixing it with `!`
# see Maven documentation for more details:
# https://maven.apache.org/surefire/maven-surefire-plugin/examples/single-test.html#Multiple_Formats_in_One
mvnParameters = lib.escapeShellArgs [
"-Dsurefire.failIfNoSpecifiedTests=false"
"-Dtest=!JavaDecompilerTest#basicTest,!JavaDecompilerTest#patternMatchingTest"
];
# old mvnHash of 1.2.0 maven dependencies
mvnHash = "sha256-N9XC1pg6Y4sUiBWIQUf16QSXCuiAPpXEHGlgApviF4I=";
}) ساخت آفلاین
به طور پیشفرض، buildMavenPackage موارد زیر را انجام میدهد:
- اجرای
mvn package -Dmaven.repo.local=$out/.m2 $درfetchedMavenDeps
maven.buildMavenPackage {
manualMvnArtifacts = [
# add dynamic test dependencies here
"org.apache.maven.surefire:surefire-junit-platform:3.1.2"
"org.junit.platform:junit-platform-launcher:1.10.0"
];
} پلاگینهای پایدار Maven
Maven نسخههای پیشفرضی را برای پلاگینهای اصلی خود تعریف میکند، مانند maven-compiler-plugin. اگر پروژه شما
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>enforce-plugin-versions</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requirePluginVersions />
</rules>
</configuration>
</execution>
</executions>
</plugin> بندی میکند.
maven_4 یک derivation مستقل است و میتوان از آن به عنوان جایگزین در هر کجا که maven استفاده میشود بهره برد، برای
{
lib,
fetchFromGitHub,
jre,
makeWrapper,
maven_4,
}:
maven_4.buildMavenPackage (finalAttrs: {
pname = "jd-cli";
version = "1.2.1";
src = fetchFromGitHub {
owner = "intoolswetrust";
repo = "jd-cli";
tag = "jd-cli-${finalAttrs.version}";
hash = "sha256-rRttA5H0A0c44loBzbKH7Waoted3IsOgxGCD2VM0U/Q=";
};
mvnHash = "";
nativeBuildInputs = [ makeWrapper ];
installPhase = ''
runHook preInstall
mkdir -p $out/bin $out/share/jd-cli
install -Dm644 jd-cli/target/jd-cli.jar $out/share/jd-cli
makeWrapper ${jre}/bin/java $out/bin/jd-cli \
--add-flags "-jar $out/share/jd-cli/jd-cli.jar"
runHook postInstall
'';
meta = {
description = "Simple command line wrapper around JD Core Java Decompiler project";
homepage = "https://github.com/intoolswetrust/jd-cli";
license = lib.licenses.gpl3Plus;
maintainers = with lib.maintainers; [ majiir ];
};
}) maven_4 همان کمکرسان buildMavenPackage را مشابه maven ارائه میدهد (ببینید )، بنابراین تمام الگوهای مستندشده در بالا به همان صورت اعمال میشوند. توجه داشته باشید که وابستگیهای Maven که توسط Maven 4 حلوفصل میشوند با موارد حلشده توسط Maven 3 متفاوت هستند، بنابراین mvnHash هنگام جابهجایی بین این دو باید دوباره محاسبه شود.
استفاده دستی از mvn2nix
هشدار
این روش دیگر
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>io.github.fzakaria</groupId> <artifactId>maven-demo</artifactId> <version>1.0</version> <packaging>jar</packaging> <name>NixOS Maven Demo</name> <dependencies> <dependency> <groupId>com.vdurmont</groupId> <artifactId>emoji-java</artifactId> <version>5.1.1</version> </dependency> </dependencies> </project>فایل کلاس اصلی ما بسیار ساده خواهد بود:
import com.vdurmont.emoji.EmojiParser; public class Main { public static void main(String[] args) { String str = "NixOS :grinning: is super cool :smiley:!"; String result = EmojiParser.parseToUnicode(str); System.out.println(result); } }این پروژه نمونه را میتوانید در https://github.com/fzakaria/nixos-maven-example
# run this step within the project's source repository ❯ mvn org.nixos.mvn2nix:mvn2nix-maven-plugin:mvn2nix ❯ cat project-info.json | jq | head { "project": { "artifactId": "maven-demo", "groupId": "org.nixos", "version": "1.0", "classifier": "", "extension": "jar", "dependencies": [ { "artifactId": "maven-resources-plugin",این فایل سپس به تابع
buildMavenداده میشود و ۲ صفت بازمیگرداند.
repo: یک مخزن Maven که یک مزرعه پیوند نماد
{ pkgs ? import <nixpkgs> { }, }: with pkgs; (buildMaven ./project-info.json).repoمزیت این روش نسبت به فراخوانی دوگانه، همانطور که در ادامه خواهیم دید، این است که ورودی /nix/store یک linkFarm از تمامی بستههاست، بنابراین تغییر در مجموعه وابستگیهای شما مستلزم بارگیری همهچیز از ابتدا نخواهد بود.
❯ tree $(nix-build --no-out-link build-maven-repository.nix) | head /nix/store/g87va52nkc8jzbmi1aqdcf2f109r4dvn-maven-repository ├── antlr │ └── antlr │ └── 2.7.2 │ ├── antlr-2.7.2.jar -> /nix/store/d027c8f2cnmj5yrynpbq2s6wmc9cb559-antlr-2.7.2.jar │ └── antlr-2.7.2.pom -> /nix/store/mv42fc5gizl8h5g5vpywz1nfiynmzgp2-antlr-2.7.2.pom ├── avalon-framework │ └── avalon-framework │ └── 4.1.3 │ ├── avalon-framework-4.1.3.jar -> /nix/store/iv5fp3955w3nq28ff9xfz86wvxbiw6n9-avalon-framework-4.1.3.jar"sandboxed" in glossary: sandboxed -> ایزوله شده (Sandboxed). In prose, "ایزوله شده" fits well. "download" in glossary: download -> بارگیری.
Let's
{ lib, stdenv, maven, }: stdenv.mkDerivation { name = "maven-repository"; buildInputs = [ maven ]; src = ./.; # or fetchFromGitHub, cleanSourceWith, etc buildPhase = '' runHook preBuild mvn package -Dmaven.repo.local=$out runHook postBuild ''; # keep only *.{pom,jar,sha1,nbm} and delete all ephemeral files with lastModified timestamps inside installPhase = '' runHook preInstall find $out -type f \ -name \*.lastUpdated -or \ -name resolver-status.properties -or \ -name _remote.repositories \ -delete runHook postInstall ''; # don't do any fixup dontFixup = true; outputHashAlgo = null; outputHashMode = "recursive"; # replace this with the correct SHA256 outputHash = lib.fakeHash; }ساخت (Build) با شکست مواجه خواهد شد و
outputHashمورد انتظار برای قرار دادن را به شما اعلام میکند. هنگامی که هش را تنظیم کردید، ساخت با
❯ tree $(nix-build --no-out-link double-invocation-repository.nix) | head /nix/store/8kicxzp98j68xyi9gl6jda67hp3c54fq-maven-repository ├── backport-util-concurrent │ └── backport-util-concurrent │ └── 3.1 │ ├── backport-util-concurrent-3.1.pom │ └── backport-util-concurrent-3.1.pom.sha1 ├── classworlds │ └── classworlds │ ├── 1.1 │ │ ├── classworlds-1.1.jarاگر بسته شما از وابستگیهای SNAPSHOT یا بازههای نسخه استفاده میکند، احتمال زیادی وجود دارد که با گذشت زمان، هش خروجی شما تغییر کند؛ زیرا وابستگیهای حلشده ممکن است تغییر کنند. از این رو، این روش کمتر از استفاده از
buildMavenتوصیه میشود.ساخت یک فایل JAR
صرفنظر از اینکه کدام راهبرد در بالا انتخاب شده باشد، گام ساخت derivation / اشتقاق ساخت یکسان است.
{ stdenv, maven, callPackage, }: let # pick a repository derivation, here we will use buildMaven repository = callPackage ./build-maven-repository.nix { }; in stdenv.mkDerivation (finalAttrs: { pname = "maven-demo"; version = "1.0"; src = fetchTarball "https://github.com/fzakaria/nixos-maven-example/archive/main.tar.gz"; buildInputs = [ maven ]; buildPhase = '' runHook preBuild echo "Using repository ${repository}" mvn --offline -Dmaven.repo.local=${repository} package; runHook postBuild ''; installPhase = '' runHook preInstall install -Dm644 target/${finalAttrs.pname}-${finalAttrs.version}.jar $out/share/java runHook postInstall ''; })راهنمایی
ما کتابخانه را در
$out/share/javaقرار میدهیم، زیرا بسته JDK دارای یک stdenv setup hook است که تمام فایلهای JAR موجود در پوشههایshare/javaورودیهای ساخت را به محیط CLASSPATH اضافه میکند.
❯ tree $(nix-build --no-out-link build-jar.nix) /nix/store/7jw3xdfagkc2vw8wrsdv68qpsnrxgvky-maven-demo-1.0 └── share └── java └── maven-demo-1.0.jar 2 directories, 1 fileفایل JAR قابل اجرا
مثال قبلی یک فایل
jarمیسازد اما این فایلی نیست که بتوان آن را اجرا کرد.شما باید آن را با
java -jar $out/share/java/output.jarاستفاده کنید و مطمئن شوید که وابستگیها مورد نیاز را در classpath ارائه دادهاید.در ادامه نحوه استفاده از
makeWrapperتوضیح داده میشود تا derivation یک فایل اجرایی تولید کند که فایل JAR ساختهشده توسط شما را اجرا خواهد کرد.ما از همان مخزنی که در بالا ساختیم (خواه double invocation باشد یا
{ stdenv, maven, callPackage, makeWrapper, jre, }: let repository = callPackage ./build-maven-repository.nix { }; in stdenv.mkDerivation (finalAttrs: { pname = "maven-demo"; version = "1.0"; src = fetchTarball "https://github.com/fzakaria/nixos-maven-example/archive/main.tar.gz"; nativeBuildInputs = [ makeWrapper ]; buildInputs = [ maven ]; buildPhase = '' runHook preBuild echo "Using repository ${repository}" mvn --offline -Dmaven.repo.local=${repository} package; runHook postBuild ''; installPhase = '' runHook preInstall mkdir -p $out/bin classpath=$(find ${repository} -name "*.jar" -printf ':%h/%f'); install -Dm644 target/maven-demo-${finalAttrs.version}.jar $out/share/java # create a wrapper that will automatically set the classpath # this should be the paths from the dependency derivation makeWrapper ${jre}/bin/java $out/bin/maven-demo \ --add-flags "-classpath $out/share/java/maven-demo-${finalAttrs.version}.jar:''${classpath#:}" \ --add-flags "Main" runHook postInstall ''; })فایل MANIFEST از طریق افزونه Maven
این روش زمانی ایدهآل است که شما مالک پروژه باشید و بخواهید
pom.xmlخود را برای تنظیم CLASSPATH در داخل آن تغییر دهید.فایل
pom.xmlرا گسترش دهید تا یک فایل JAR با مانفیست زیر ایجاد شود:
<build> <plugins> <plugin> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>../../repository/</classpathPrefix> <classpathLayoutType>repository</classpathLayoutType> <mainClass>Main</mainClass> </manifest> <manifestEntries> <Class-Path>.</Class-Path> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build>پلاگین بالا به فایل JAR دستور میدهد که وابستگیهای لازم را در پوشه نسبی
lib/جستجو کند. چیدمان این پوشه نیز به سبک مخزن Maven است.
❯ unzip -q -c $(nix-build --no-out-link runnable-jar.nix)/share/java/maven-demo-1.0.jar META-INF/MANIFEST.MF Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Built-By: nixbld Class-Path: . ../../repository/com/vdurmont/emoji-java/5.1.1/emoji-jav a-5.1.1.jar ../../repository/org/json/json/20170516/json-20170516.jar Created-By: Apache Maven 3.6.3 Build-Jdk: 1.8.0_265 Main-Class: Mainما درایویشن بالا را تغییر خواهیم داد تا یک پیوند نمادین (symlink) به مخزن خود اضافه کنیم تا در طول
installPhaseبرای JAR ما قابل دسترسی باشد.
{ stdenv, maven, callPackage, makeWrapper, jre, }: let # pick a repository derivation, here we will use buildMaven repository = callPackage ./build-maven-repository.nix { }; in stdenv.mkDerivation (finalAttrs: { pname = "maven-demo"; version = "1.0"; src = fetchTarball "https://github.com/fzakaria/nixos-maven-example/archive/main.tar.gz"; nativeBuildInputs = [ makeWrapper ]; buildInputs = [ maven ]; buildPhase = '' runHook preBuild echo "Using repository ${repository}" mvn --offline -Dmaven.repo.local=${repository} package; runHook postBuild ''; installPhase = '' runHook preInstall mkdir -p $out/bin # create a symbolic link for the repository directory ln -s ${repository} $out/repository install -Dm644 target/maven-demo-${finalAttrs.version}.jar $out/share/java # create a wrapper that will automatically set the classpath # this should be the paths from the dependency derivation makeWrapper ${jre}/bin/java $out/bin/maven-demo \ --add-flags "-jar $out/share/java/maven-demo-${finalAttrs.version}.jar" runHook postInstall ''; })نکته
اسکریپت ما به جای
jdkیک وابستگی بهjreایجاد میکند تا بستار زمان اجرای لازم برای اجرای برنامه را محدود کند.این کار یک اسکریپت شل قابل اجرا در اختیار شما قرار میدهد که فایل JAR شما را همراه با تمام وابستگیهای در دسترس اجرا میکند.
❯ tree $(nix-build --no-out-link runnable-jar.nix) /nix/store/8d4c3ibw8ynsn01ibhyqmc1zhzz75s26-maven-demo-1.0 ├── bin │ └── maven-demo ├── repository -> /nix/store/g87va52nkc8jzbmi1aqdcf2f109r4dvn-maven-repository └── share └── java └── maven-demo-1.0.jar ❯ $(nix-build --no-out-link --option tarball-ttl 1 runnable-jar.nix)/bin/maven-demo NixOS 😀 is super cool 😃!