【问题标题】:Gradle multiple jars from single source folder从单个源文件夹 Gradle 多个 jar
【发布时间】:2013-11-29 06:34:20
【问题描述】:

目前我们有一个名为src 的单一源文件夹的项目结构,其中包含三个模块的源代码。我想做的是:

1) 编译源代码。这可以通过 sourceSets 定义轻松完成:

sourceSets {
    main {
        java {
            srcDir 'src'
        }
    }
}

2) 将编译结果放入三个jar中。我通过三个“jar”类型的任务来做到这一点:

我现在通过三个单独的任务来做这件事:

  • util.jar

    task utilJar(type: Jar) {
        from(sourceSets.main.output) {
            include "my/util/package/**"
        }
    }
    
  • client.jar

    task clientJar(type: Jar) {
        from(sourceSets.main.output) {
            include "my/client/package/**"
        }
    }
    
  • server.jar

    task serverJar(type: Jar) {
        from(sourceSets.main.output) {
            include "**"
        }
        excludes.addAll(utilJar.includes)
        excludes.addAll(clientJar.includes)
    }
    

问题是server.jar 应该包含所有未包含在client.jarutil.jar 中的类。在 ant 构建脚本中,我们使用 difference ant 任务解决了这个问题。这如何在 gradle 中完成(我目前的方法不起作用)?

也许我的方法是完全错误的。请指教。

附:至于现在我们不能更改项目源代码文件夹结构。

【问题讨论】:

    标签: java jar gradle


    【解决方案1】:

    我将在此处发布我的工作解决方案作为答案(我在 gradle 的论坛上有提示)。

    gradle 中的范围是非常奇怪的东西 :) 我认为每个任务定义都会创建某个“任务”类的对象,在这种特殊情况下类似于“JarTask”。然后我可以从 build.gradle 脚本中的任何位置访问该类的任何属性。但是,我找到了唯一可以看到模式的地方,这些模式包含在 jar 文件中 - 在任务的 from 块内。所以我现在的工作解决方案是:

    1) 定义一个项目级集合以包含要从server.jar 中排除的模式

    2) 排除serverJar 任务的from 块中的所有模式。

    请看下面的最终版本

    sourceSets {  
        main {  
            java {  
                srcDir 'src'  
            }  
        }  
    } 
    
    // holds classes included into client.jar and util.jar, so they are to be excluded from server.jar
    ext.serverExcludes = []
    
    // util.jar
    task utilJar(type: Jar) {  
        from(sourceSets.main.output) {  
            include "my/util/package/**" 
            project.ext.serverExcludes.addAll(includes)
        }  
    }
    
    // client.jar
    task clientJar(type: Jar) {  
        from(sourceSets.main.output) {  
            include "my/client/package/**"
            project.ext.serverExcludes.addAll(includes)
        }  
    }
    
    // server.jar
    task serverJar(type: Jar) {  
        from(sourceSets.main.output) {  
            exclude project.ext.serverExcludes
        }  
    }
    

    【讨论】:

    • 这很好,但是你如何引用来自其他项目的单独的 .jar 文件 - 例如你怎么能把client.jar 拉进另一个子项目?
    • @z0r 我们只是将工件发布到存储库,然后将它们用作子项目中的依赖项。附言很抱歉延迟回复。
    【解决方案2】:

    我也原则上同意接受的答案。 我发现了一个项目,其中客户端需要两个 JAR,基本上是同一个文件,除了 Manifest 仅在 Class-Path 键上有所不同。

    jar {
        manifest {
            attributes(
                    "Main-Class": platformMainClass,
                    "Implementation-Title": platformDisplayName,
                    "Implementation-Description": platformDescription,
                    "Platform-Version": platformVersion,
                    "Implementation-Version": version,
                    "Build-Assembly-User": System.getProperty("user.name"),
                    "Build-Assembly-Date": new java.util.Date().toString(),
                    "Class-Path": configurations.compile.collect { "lib/"+it.getName() }.join(' ')
            )
        }
    
        duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    
        exclude( [ 'log4j*.properties', 'uk/gov/acme/secret/product/server/**' ])
    }
    

    同样的清单和源代码则为:

    task applicationClientJar(type: Jar, description: "Creates the Application  Client JAR file.") {
        dependsOn compileJava
        manifest {
            attributes(
                    "Main-Class": platformMainClass,
                    "Implementation-Title": platformDisplayName,
                    "Implementation-Description": platformDescription,
                    "Platform-Version": platformVersion,
                    "Implementation-Version": version,
                    "Assembly-Date": new java.util.Date().toString()
            )
        }
        archiveName = "acme-client-${platformVersion}.jar"
        destinationDir = file("${buildDir}/libs")
        from sourceSets.main.output
    
        duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    
        exclude( [ 'log4j*.properties', 'uk/gov/acme/secret/product/server/**'     }
    

    所以 Grzegorz 表示法是正确的,因为 Gradle 应该知道 GAV 有两个不同的 JAR。多模块是首选。

    compile "uk.gov.acme.secret:acme:1.0"  // CORE
    compile "uk.gov.acme.secret:acme-client:1.0"
    

    为此配置的唯一方法是使用 Multi-Module Gradle 项目,然后将编译和/或部署依赖项添加到核心/主项目。

    project(':common:acme-micro-service-webapp') {
        dependencies {
            compile project(':common:acme-core')
        }
    }
    

    在“acme-micro-service-webapp”项目中,这确保了依赖的“common:acme-core”首先被编译。

    PS:我还在努力寻找更好的解决方案。

    PS PS:如果您也使用 Maven,可能会挂上“安装”任务。

    【讨论】:

    • 如果项目目录结构必须保持不变(如某些非标准项目结构),这个答案非常巧妙。 Grzegorz Żur 的答案是一个干净的方法,但需要修改目录结构,这对我来说不是一个选择。我只有一个以上的具有主要功能的类,并且每个类都需要一个 jar。我会将这个 tasks.build.dependsOn(['JarOne', 'JarTwo', 'applicationClientJar']) 添加到 build.gradle 文件中,以确保一个简单的 gradle build 将 jar 任务添加到其过程中。
    【解决方案3】:

    我们公司也有同样的问题,即。难以迁移到“良好”项目结构中的遗留代码,并且需要从同一代码库构建多个 jar。我们决定定义不同的 sourceSet 并使用标准 Gradle 构建每个 sourceSet。

    然后我们使用迭代器为每个 sourceSet 添加 jar 和 javadoc 任务:

    sourceSets.all { SourceSet sourceSet ->
        Task jarTask = tasks.create("jar" + sourceSet.name, Jar.class)
        jarTask.from(sourceSet.output)
        // Configure other jar task properties: group, description, manifest etc
    
        Task javadocTask = tasks.create("javadoc" + sourceSet.name, Javadoc.class)
        javadocTask.setClasspath(sourceSet.output + sourceSet.compileClasspath)
        javadocTask.setSource(sourceSet.allJava)
        // Extra config for the javadoc task: group, description etc
    
        Task javadocJarTask = tasks.create("javadocJar" + sourceSet.name, Jar.class)
        javadocJarTask.setClassifier("javadoc") // adds "-javadoc" to the name of the jar
        javadocJarTask.from(javadocTask.outputs)
        // Add extra config: group, description, manifest etc
    }
    

    【讨论】:

      【解决方案4】:

      我认为这种方法是错误的。我建议创建一个包含 3 个子项目的项目。

      project
      - util
      - server (depends on util)
      - client (depends on util)
      

      如果由于某种原因您无法更改类结构,请使用这种构建文件:

      settings.gradle

      include 'util', 'client', 'server'
      

      build.gradle

      subprojects {
          apply plugin: 'java'
      }
      
      project(':util') {
          sourceSets {
              main {
                  java {
                      srcDir '../src'
                      include 'util/**'
                  }
              }
          }
      }
      
      project(':server') {
          sourceSets {
              main {
                  java {
                      srcDir '../src'
                      include 'server/**'
                  }
              }
          }
          dependencies {
              compile project(':util')
          }
      }
      
      project(':client') {
          sourceSets {
              main {
                  java {
                      srcDir '../src'
                      include 'client/**'
                  }
              }
          }
          dependencies {
              compile project(':util')
          }
      }
      

      您仍然需要子项目的目录,但源代码在您想要的地方。

      当您运行gradle assemble 时,您将拥有 3 个带有不同类集的 jar。这个解决方案的优点是我们制作了一个具有正确依赖关系的正确 Gradle 多模块项目,而不仅仅是构建 jar 的任务。

      请阅读Multi-Project Builds

      【讨论】:

      • 感谢您的评论。我已经考虑过这种方法,但是完全不改变目录结构是非常可取的。主要原因是我们有很多 SVN 分支(超过 50 个),在目录结构更改后合并它们应该是一个真正的痛苦(你知道,树冲突)。另一个原因是utilclient 相互依赖,所以我猜它们应该在一个单独的模块中,这会带来同样的问题。我知道这在我们的源代码结构设计中是一团糟,但我相信 gradle 可以做 ant 可以做的一切:)
      • @vitalidze 我提出的解决方案是不改变目录结构。额外的空目录有问题吗?你有依赖循环吗?
      • 问题是类没有被文件夹分隔(即客户端、服务器、实用程序)。例如,我在客户端jar中包含'a/b'和'z/x/y',util包括'q/w/e',服务器应该包括所有的rest类,可能在'a','z '、'z/x'、'q'、'q/w'。
      • 我对此投了反对票,因为在某些情况下,您需要一个项目来生成多个工件,而这正是 OP 所寻找的。​​span>
      • 如果你使用 Gradle >= 3,compile 配置已被弃用,而implementation 已被弃用。见Migrate to Android Plugin for Gradle 3.0.0
      猜你喜欢
      • 2016-07-28
      • 1970-01-01
      • 1970-01-01
      • 2011-09-26
      • 1970-01-01
      • 2019-09-28
      • 2012-09-06
      • 1970-01-01
      • 2014-01-03
      相关资源
      最近更新 更多