aboutsummaryrefslogtreecommitdiffstats
path: root/vespa-application-maven-plugin/src/main/java/com/yahoo/container/plugin/mojo/ApplicationMojo.java
blob: aff505f934f253b3a15d70e95b076ff027e05a32 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.container.plugin.mojo;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.NameFileFilter;
import org.apache.commons.io.filefilter.NotFileFilter;
import org.apache.commons.io.filefilter.OrFileFilter;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Collections;
import java.util.List;

/**
 * @author Tony Vaagenes
 */
@Mojo(name = "packageApplication", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true)
public class ApplicationMojo extends AbstractMojo {

    private static final List<String> IGNORED_FILES = List.of(".DS_Store");

    @Parameter(defaultValue = "${project}", readonly = true)
    protected MavenProject project;

    @Parameter(property = "vespaversion")
    private String vespaversion;

    @Parameter(defaultValue = "src/main/application")
    private String sourceDir;

    @Parameter(defaultValue = "target/application")
    private String destinationDir;

    @Override
    public void execute() throws MojoExecutionException {
        File applicationPackage = new File(project.getBasedir(), sourceDir);
        File applicationDestination = new File(project.getBasedir(), destinationDir);
        copyApplicationPackage(applicationPackage, applicationDestination);
        addBuildMetaData(applicationDestination);

        File componentsDir = createComponentsDir(applicationDestination);
        copyModuleBundles(project.getBasedir(), componentsDir);
        copyBundlesForSubModules(componentsDir);

        try {
            Compression.zipDirectory(applicationDestination, "");
        } catch (Exception e) {
            throw new MojoExecutionException("Failed zipping application.", e);
        }
    }

    /** Writes meta data about this package if the destination directory exists. */
    private void addBuildMetaData(File applicationDestination) throws MojoExecutionException {
        if ( ! applicationDestination.exists()) return;

        if (vespaversion == null)
            vespaversion = project.getPlugin("com.yahoo.vespa:vespa-application-maven-plugin").getVersion();

        Version compileVersion = Version.from(vespaversion);
        if (compileVersion.isSnapshot()) return;

        MavenProject current = project;
        while (current.getParent() != null && current.getParent().getParentArtifact() != null)
            current = current.getParent();

        Version parentVersion = null;
        Artifact parentArtifact = current.getParentArtifact();
        if (parentArtifact != null && isVespaParent(parentArtifact.getGroupId())) {
            try {
                parentVersion = Version.from(parentArtifact.getSelectedVersion().toString());
            } catch (ArtifactResolutionException e) {
                parentVersion = Version.from(parentArtifact.getVersion());
            }
            if (parentVersion.compareTo(compileVersion) < 0)
                throw new IllegalArgumentException("compile version (" + compileVersion + ") cannot be higher than parent version (" + parentVersion + ")");
        }

        String metaData = String.format("""
                                        {
                                          "compileVersion": "%s",
                                          "buildTime": %d,
                                          "parentVersion": %s
                                        }
                                        """,
                                        compileVersion,
                                        System.currentTimeMillis(),
                                        parentVersion == null ? null : "\"" + parentVersion + "\"");
        try {
            Files.writeString(applicationDestination.toPath().resolve("build-meta.json"), metaData);
        }
        catch (IOException e) {
            throw new MojoExecutionException("Failed writing compile version and build time.", e);
        }
    }

    static boolean isVespaParent(String groupId) {
        return groupId.matches("(com\\.yahoo\\.vespa|ai\\.vespa)(\\..+)?");
    }

    private void copyBundlesForSubModules(File componentsDir) throws MojoExecutionException {
        List<String> modules = emptyListIfNull(project.getModules());
        for (String module : modules) {
            File moduleDir = new File(project.getBasedir(), module);
            if (moduleDir.exists()) {
                copyModuleBundles(moduleDir, componentsDir);
            }
        }
    }

    private File createComponentsDir(File applicationDestination) throws MojoExecutionException {
        File componentsDir = new File(applicationDestination, "components");
        componentsDir.mkdirs();
        if (!componentsDir.exists() || !componentsDir.isDirectory()) {
            throw new MojoExecutionException("Failed creating components directory (" + componentsDir + ")");
        }
        return componentsDir;
    }

    private void copyApplicationPackage(File applicationPackage, File applicationDestination) throws MojoExecutionException {
        if (applicationPackage.exists()) {
            try {
                FileUtils.copyDirectory(applicationPackage, applicationDestination, ignoredFilesFilter());
            } catch (IOException e) {
                throw new MojoExecutionException("Failed copying applicationPackage", e);
            }
        }
    }

    static FileFilter ignoredFilesFilter() {
        var ioFileFilters = IGNORED_FILES.stream()
                .map(NameFileFilter::new)
                .map(IOFileFilter.class::cast)
                .toList();
        return new NotFileFilter(new OrFileFilter(ioFileFilters));
    }

    private void copyModuleBundles(File moduleDir, File componentsDir) throws MojoExecutionException {
        File moduleTargetDir = new File(moduleDir, "target");
        if (moduleTargetDir.exists()) {
            File[] bundles = moduleTargetDir.listFiles((dir, name) -> name.endsWith("-deploy.jar") ||
                                                                      name.endsWith("-bundle.jar") ||
                                                                      name.endsWith("-jar-with-dependencies.jar"));
            if (bundles == null) return;
            for (File bundle : bundles) {
                try {
                    copyFile(bundle, new File(componentsDir, bundle.getName()));
                    getLog().info("Copying bundle to application: " + bundle.getName());
                } catch (IOException e) {
                    throw new MojoExecutionException("Failed copying bundle " + bundle, e);
                }
            }
        }
    }

    private void copyFile(File source, File destination) throws IOException {
        try (FileInputStream sourceStream = new FileInputStream(source);
             FileOutputStream destinationStream = new FileOutputStream(destination)) {
            sourceStream.transferTo(destinationStream);
        }
    }

    private static <T> List<T> emptyListIfNull(List<T> modules) {
        return modules == null ? Collections.emptyList(): modules;
    }

}