如何将清单部分与 Gradle 和 shadowJar 合并

Posted

技术标签:

【中文标题】如何将清单部分与 Gradle 和 shadowJar 合并【英文标题】:How to merge Manifest sections with Gradle and shadowJar 【发布时间】:2021-05-25 18:02:33 【问题描述】:

我需要什么

我们使用 Gradle 和 shadowJar 打包我们的产品。我们使用的一些库,使用individual sections in Jar Manifests,特别是诸如 Implementation-Title 和 实施版本。这些有时会显示在我们的产品(输出)中,所以我希望它们能够在 shawdowJar-Process 中存活下来。

示例

lib1.jar/META-INF/MANIFEST.MF

Manifest-Version: 1.0
...

Name: org/some/lib
...
Implementation-Title: someLib
Implementation-Version: 2.3
...

lib2.jar/META-INF/MANIFEST.MF

Manifest-Version: 1.0
...

Name: org/some/other/lib
...
Implementation-Title: someOtherLib
Implementation-Version: 5.7-RC
...

=> product.jar/META-INF/MANIFEST.MF

Manifest-Version: 1.0
...

Name: org/some/lib
...
Implementation-Title: someLib
Implementation-Version: 2.3
...

Name: org/some/other/lib
...
Implementation-Title: someOtherLib
Implementation-Version: 5.7-RC
...

我发现了什么

manipulate the resulting Manifest 使用 shadowJar 相当容易:
project.shadowJar 
    manifest 
        attributes(["Implementation-Title" : "someLib"], "org/some/lib")
        attributes(["Implementation-Title" : "someOtherLib"], "org/some/other/lib")
    

静态生成我想要的。

shadowJar 可以为我提供a list of dependencies。但是,当我像这样遍历 FileCollection 时
project.shadowJar 
    manifest 
        for (dependency in includedDependencies) 
            // read in jar file and set attributes
        
    

Gradle 不高兴:“在依赖解析中包含依赖配置 ':project:products::compile' 后,无法更改依赖配置。”

当我定义一个新任务时
def dependencies = [];
project.tasks.register('resolveDependencies') 
    doFirst 
        gradleProject.configurations.compile.resolvedConfiguration.resolvedArtifacts.each 
            dependencies.add(it.file)
        
    

project.tasks['shadowJar'].dependsOn(project.tasks['resolveDependencies']);

project.shadowJar 
    manifest 
        // dependencies will be empty when this code is called
        for (dependency in dependencies) 
            // read in jar file and set attributes
        
    

依赖没有及时解决。

我想知道的

如何在不影响 Gradle 的情况下访问依赖项?或者,是否有另一种方法可以将命名的各个部分与 shadowJar 合并?

【问题讨论】:

【参考方案1】:

根据https://github.com/johnrengelman/shadow/issues/369,应该使用shadowJar的Transformer接口来做到这一点。

所以来了:

import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer;
import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext;

import java.io.ByteArrayOutputStream;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
import java.util.Map.Entry;

import shadow.org.apache.tools.zip.ZipOutputStream;
import shadow.org.apache.tools.zip.ZipEntry;
import shadow.org.codehaus.plexus.util.IOUtil;

import org.gradle.api.file.FileTreeElement;

import static java.nio.charset.StandardCharsets.*
import static java.util.jar.JarFile.*;

/**ManifestVersionMergeTransformer appends all version information sections from manifest files to the resulting manifest file.
  * @author Robert Lichtenberger
  */
public class ManifestMergeTransformer implements Transformer 

    String includePackages; // regular expression that must match a given package
    String excludePackages; // regular expression that must not match a given package

    private Manifest manifest;

    @Override
    public boolean canTransformResource(FileTreeElement element) 
        MANIFEST_NAME.equalsIgnoreCase(element.relativePath.pathString);
    

    @Override
    public void transform(TransformerContext context) 
        if (manifest == null) 
            manifest = new Manifest(context.is);
         else 
            Manifest toMerge = new Manifest(context.is);
            for (Entry<String, Attributes> entry : toMerge.getEntries().entrySet()) 
                if (mustInclude(entry.getKey())) 
                    manifest.getEntries().put(entry.getKey(), entry.getValue());
                
            
        
        IOUtil.close(context.is);
    

    private boolean mustInclude(String packageName) 
        return (includePackages == null || packageName.matches(includePackages)) && (excludePackages == null || !packageName.matches(excludePackages));
    

    @Override
    public boolean hasTransformedResource() 
        return true;
    

    @Override
    public void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) 
        ZipEntry entry = new ZipEntry(MANIFEST_NAME);
        entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time);
        os.putNextEntry(entry);
        if (manifest != null) 
            ByteArrayOutputStream manifestContents = new ByteArrayOutputStream();
            manifest.write(manifestContents);
            os.write(manifestContents.toByteArray());
        
    

【讨论】:

以上是关于如何将清单部分与 Gradle 和 shadowJar 合并的主要内容,如果未能解决你的问题,请参考以下文章

如何将 PayPal sdk 与 Gradle Android 集成

在 Android Gradle 构建中禁用清单合并

Android多版本flavor配置之资源文件和清单文件合并介绍

Gradle - 没有主要清单属性

Android Gradle 插件组件化中的 Gradle 构建脚本实现 ③ ( 在 Gradle 构建脚本中实现 AndroidManifest.xml 清单文件切换设置 )

Android多版本flavor配置之资源文件和清单文件合并介绍