Introduce maven plugin that can check that a native lib can be used w… (#10)

Motivation:

In the past we messed up released native libs as we did compile on the wrong GLIBC version. This can lead to situations when its not possible anymore to use the native libs an a specific system. We should be able to verify that all works as expected during the build / release process.

Modifications:

- Make netty-build a multi maven module project
- Move all existing code to the common module
- Introduce glibccheck-maven-plugin that can be used to validate that a native lib will be usable with a specific GLIBC version

Result:

Provide maven plugin that can be used to verify linking of native lib to correct GLIBC version
This commit is contained in:
Norman Maurer 2020-11-05 13:55:43 +01:00 committed by GitHub
parent ddd2826d7d
commit af09be8634
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 410 additions and 19 deletions

39
common/pom.xml Normal file
View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2020 The Netty Project
~
~ The Netty Project licenses this file to you under the Apache License,
~ version 2.0 (the "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at:
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
~ License for the specific language governing permissions and limitations
~ under the License.
-->
<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">
<parent>
<artifactId>netty-build</artifactId>
<groupId>io.netty</groupId>
<version>27-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common</artifactId>
<name>Netty/Build/Common</name>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -17,7 +17,6 @@ package io.netty.build.checkstyle;
import com.puppycrawl.tools.checkstyle.api.AuditEvent; import com.puppycrawl.tools.checkstyle.api.AuditEvent;
import com.puppycrawl.tools.checkstyle.api.AutomaticBean; import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.Filter; import com.puppycrawl.tools.checkstyle.api.Filter;
import java.util.regex.Pattern; import java.util.regex.Pattern;

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2020 The Netty Project
~
~ The Netty Project licenses this file to you under the Apache License,
~ version 2.0 (the "License"); you may not use this file except in compliance
~ with the License. You may obtain a copy of the License at:
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
~ License for the specific language governing permissions and limitations
~ under the License.
-->
<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">
<parent>
<artifactId>netty-build</artifactId>
<groupId>io.netty</groupId>
<version>27-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>maven-plugin</packaging>
<artifactId>glibccheck-netty-build-maven-plugin</artifactId>
<name>Netty/Build/GlibcCheck</name>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
</dependency>
<!-- dependencies to annotations -->
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,177 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.build.maven;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Verify that a native library is usable on a specific GLIBC version.
*/
@Mojo(name= "versioncheck", defaultPhase = LifecyclePhase.VERIFY)
public final class GlibcVersionCheckMojo extends AbstractMojo {
// Pattern / regex to extract the GLIBC version dependencies.
private static final Pattern GLIBC_PATTERN = Pattern.compile(".+ GLIBC_([0-9]).([0-9]+)(.([0-9]+))? (.+)");
@Parameter( property = "versioncheck.maxGlibcVersion", required = true)
private String maxGlibcVersion;
@Parameter( property = "versioncheck.objdump")
private String objdump;
@Parameter( property = "versioncheck.nativeLib", required = true)
private File nativeLib;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (objdump == null) {
// Try to detect the objdump installation.
String osname = System.getProperty("os.name", "").toLowerCase(Locale.US)
.replaceAll("[^a-z0-9]+", "");
boolean osx = osname.startsWith("macosx") || osname.startsWith("osx");
if (osx) {
objdump = checkObjdumpExists("/usr/local/opt/binutils/bin/gobjdump", "brew install binutils");
} else {
objdump = checkObjdumpExists("/usr/bin/objdump", "apt-get|yum install binutils");
}
}
String[] versionParts = maxGlibcVersion.split("\\.");
if (versionParts.length < 1 || versionParts.length > 3) {
throw new MojoExecutionException("Unable to parse maxGlibcVersion: " + maxGlibcVersion);
}
// Parse the major, minor and bugfix versions
final int majorVersion;
final int minorVersion;
final int bugFixVersion;
try {
majorVersion = Integer.parseInt(versionParts[0]);
if (versionParts.length == 1) {
minorVersion = 0;
bugFixVersion = 0;
} else {
minorVersion = Integer.parseInt(versionParts[1]);
if (versionParts.length == 2) {
bugFixVersion = 0;
} else {
bugFixVersion = Integer.parseInt(versionParts[2]);
}
}
} catch (NumberFormatException e) {
throw new MojoExecutionException("Unable to parse maxGlibcVersion: " + maxGlibcVersion, e);
}
if ( !nativeLib.isFile() ) {
throw new MojoExecutionException(nativeLib + " is not a file");
}
InputStream in = null;
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
String cmd = objdump + " -T " + nativeLib;
Process process = Runtime.getRuntime()
.exec(cmd);
int rcode = process.waitFor();
if (rcode != 0) {
throw new MojoExecutionException(cmd + " exit with return code " + rcode);
}
in = process.getInputStream();
byte[] bytes = new byte[8192];
int i;
while ((i = in.read(bytes)) != -1) {
out.write(bytes, 0, i);
}
out.flush();
check(majorVersion, minorVersion, bugFixVersion, new String(out.toByteArray(), StandardCharsets.UTF_8));
} catch (InterruptedException e) {
throw new MojoExecutionException("Interrupted while waiting for objdump to complete", e);
} catch (IOException e) {
throw new MojoExecutionException("Unable to execute objdump", e);
} finally {
closeSilently(in);
closeSilently(out);
}
}
private static void closeSilently(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException ignore) {
// ignore
}
}
}
// Package-private for easy testing
static void check(int major, int minor, int bugfix, String in) throws MojoFailureException {
StringTokenizer tokenizer = new StringTokenizer(in, "\n");
while (tokenizer.hasMoreElements()) {
String line = tokenizer.nextToken();
Matcher matcher = GLIBC_PATTERN.matcher(line);
if (matcher.matches()) {
int foundMajor = Integer.parseInt(matcher.group(1));
int foundMinor = Integer.parseInt(matcher.group(2));
int foundBugfix = matcher.group(4) == null ? 0 : Integer.parseInt(matcher.group(4));
String function = matcher.group(5).trim();
if (foundMajor > major) {
failure(major, minor, bugfix, foundMajor, foundMinor, foundBugfix, function);
} else if (foundMajor == major) {
if (foundMinor > minor) {
failure(major, minor, bugfix, foundMajor, foundMinor, foundBugfix, function);
} else if (foundMinor == minor) {
if (foundBugfix > bugfix) {
failure(major, minor, bugfix, foundMajor, foundMinor, foundBugfix, function);
}
}
}
}
}
}
private static String checkObjdumpExists(String objdump, String installInstructions) throws MojoExecutionException {
File f = new File(objdump);
if (!f.exists() || !f.canExecute()) {
throw new MojoExecutionException("Unable to execute '" + objdump + "'." +
" May need to install it via '" + installInstructions + "'.");
}
return objdump;
}
private static void failure(int major, int minor, int bugfix, int foundMajor,
int foundMinor, int foundBugfix, String function) throws MojoFailureException{
throw new MojoFailureException("Required GLIBC " +
glibcVersion(foundMajor, foundMinor, foundBugfix) + " > " +
glibcVersion(major, minor, bugfix) + ". Required by '" + function + "'.");
}
private static String glibcVersion(int major, int minor, int bugfix) {
return major + "." + minor + "." + bugfix;
}
}

View File

@ -0,0 +1,117 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.build.maven;
import org.apache.maven.plugin.MojoFailureException;
import org.junit.Test;
public class GlibcVersionCheckMojoTest {
private static final String OBJDUMP_OUTPUT = "\n" +
"/tmp/libnetty_transport_native_epoll_x86_64.so: file format elf64-x86-64\n" +
"\n" +
"DYNAMIC SYMBOL TABLE:\n" +
"0000000000003a60 l d .init\t0000000000000000 .init\n" +
"0000000000000000 w DF *UND*\t0000000000000000 GLIBC_2.9 pipe2\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 memset\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 snprintf\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 shutdown\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 close\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.3.2 epoll_create\n" +
"0000000000000000 w D *UND*\t0000000000000000 __gmon_start__\n" +
"0000000000000000 w D *UND*\t0000000000000000 _Jv_RegisterClasses\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 recvmsg\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 uname\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.5 splice\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 getpeername\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 read\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 strncmp\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 malloc\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 fopen\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 unlink\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 setsockopt\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.7 eventfd\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 fgets\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 free\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 strlen\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 listen\n" +
"0000000000000000 w DF *UND*\t0000000000000000 GLIBC_2.2.5 __cxa_finalize\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 syscall\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 pipe\n" +
"0000000000000000 w DF *UND*\t0000000000000000 GLIBC_2.9 epoll_create1\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 sendmsg\n" +
"0000000000000000 DO *UND*\t0000000000000000 GLIBC_2.2.5 in6addr_any\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 strerror\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.3.2 epoll_ctl\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 strstr\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 strcat\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 getsockopt\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 strtol\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 getsockname\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 connect\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 memcpy\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.7 eventfd_read\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 socket\n" +
"0000000000000000 D *UND*\t0000000000000000 dladdr\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 sendfile\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 __errno_location\n" +
"0000000000000000 w DF *UND*\t0000000000000000 GLIBC_2.10 accept4\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 strcpy\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.3.2 epoll_wait\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.7 eventfd_write\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 calloc\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.3.4 __xpg_strerror_r\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 writev\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 fclose\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 recvfrom\n" +
"0000000000000000 DO *UND*\t0000000000000000 GLIBC_2.2.5 stderr\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 sendto\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 bind\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 fwrite\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.8 timerfd_create\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 fprintf\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.8 timerfd_settime\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 write\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 accept\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 clock_gettime\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 fcntl\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 open\n" +
"0000000000000000 DF *UND*\t0000000000000000 GLIBC_2.2.5 strndup\n" +
"000000000020f950 g D *ABS*\t0000000000000000 Base _end\n" +
"000000000020f864 g D *ABS*\t0000000000000000 Base _edata\n" +
"0000000000006fa0 g DF .text\t0000000000000099 Base JNI_OnUnload_netty_transport_native_epoll\n" +
"0000000000007040 g DF .text\t0000000000000099 Base JNI_OnUnload\n" +
"000000000020f864 g D *ABS*\t0000000000000000 Base __bss_start\n" +
"0000000000003a60 g DF .init\t0000000000000000 Base _init\n" +
"000000000000b0c8 g DF .fini\t0000000000000000 Base _fini\n" +
"0000000000006430 g DF .text\t000000000000000a Base JNI_OnLoad\n" +
"0000000000006440 g DF .text\t000000000000000a Base JNI_OnLoad_netty_transport_native_epoll";
@Test(expected = MojoFailureException.class)
public void testMaxVersionLower() throws MojoFailureException {
GlibcVersionCheckMojo.check(2, 8, 1, OBJDUMP_OUTPUT);
}
@Test
public void testMaxVersionMatch() throws MojoFailureException {
GlibcVersionCheckMojo.check(2,10, 0, OBJDUMP_OUTPUT);
}
@Test
public void testMaxVersionHigher() throws MojoFailureException {
GlibcVersionCheckMojo.check(2,10, 1, OBJDUMP_OUTPUT);
}
}

51
pom.xml
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
~ Copyright 2012 The Netty Project ~ Copyright 2020 The Netty Project
~ ~
~ The Netty Project licenses this file to you under the Apache License, ~ The Netty Project licenses this file to you under the Apache License,
~ version 2.0 (the "License"); you may not use this file except in compliance ~ version 2.0 (the "License"); you may not use this file except in compliance
@ -17,6 +17,10 @@
<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/maven-v4_0_0.xsd"> <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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<modules>
<module>common</module>
<module>glibccheck-maven-plugin</module>
</modules>
<parent> <parent>
<groupId>org.sonatype.oss</groupId> <groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId> <artifactId>oss-parent</artifactId>
@ -26,7 +30,7 @@
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-build</artifactId> <artifactId>netty-build</artifactId>
<packaging>jar</packaging> <packaging>pom</packaging>
<version>27-SNAPSHOT</version> <version>27-SNAPSHOT</version>
<name>Netty/Build</name> <name>Netty/Build</name>
@ -67,20 +71,31 @@
</developer> </developer>
</developers> </developers>
<dependencies> <dependencyManagement>
<dependency> <dependencies>
<groupId>com.puppycrawl.tools</groupId> <dependency>
<artifactId>checkstyle</artifactId> <groupId>org.apache.maven</groupId>
<version>8.29</version> <artifactId>maven-plugin-api</artifactId>
</dependency> <version>3.0.5</version>
</dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>junit</artifactId> <artifactId>maven-plugin-annotations</artifactId>
<version>4.13.1</version> <version>3.4</version>
</dependency> <scope>provided</scope>
</dependencies> </dependency>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>8.29</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
</dependency>
</dependencies>
</dependencyManagement>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
@ -88,8 +103,8 @@
<version>3.0</version> <version>3.0</version>
<configuration> <configuration>
<encoding>UTF-8</encoding> <encoding>UTF-8</encoding>
<source>1.6</source> <source>1.7</source>
<target>1.6</target> <target>1.7</target>
<debug>true</debug> <debug>true</debug>
<optimize>true</optimize> <optimize>true</optimize>
<showDeprecation>true</showDeprecation> <showDeprecation>true</showDeprecation>