Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build fat binary for arm64 and x86_64 on MacOS in build.sbt #12

Merged
merged 3 commits into from
Nov 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ for:
name: libsbtipcsocket.dylib

install:
- curl -sL https://github.com/sbt/sbt/releases/download/v1.3.10/sbt-1.3.10.tgz > ~/sbt-bin.tgz
- curl -sL https://github.com/sbt/sbt/releases/download/v1.4.4/sbt-1.4.4.tgz > ~/sbt-bin.tgz
- mkdir ~/sbt
- tar -xf ~/sbt-bin.tgz --directory ~/sbt
- export JAVA_HOME="/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/"
Expand All @@ -45,7 +45,7 @@ for:
install:
- sudo apt-get -y install mingw-w64
- sudo apt-get -y install clang-format
- curl -sL https://github.com/sbt/sbt/releases/download/v1.3.10/sbt-1.3.10.tgz > ~/sbt-bin.tgz
- curl -sL https://github.com/sbt/sbt/releases/download/v1.4.4/sbt-1.4.4.tgz > ~/sbt-bin.tgz
- mkdir ~/sbt
- tar -xf ~/sbt-bin.tgz --directory ~/sbt
- curl -sL https://raw.githubusercontent.com/shyiko/jabba/0.11.0/install.sh | bash && . ~/.jabba/jabba.sh
Expand Down Expand Up @@ -75,7 +75,7 @@ for:
Add-Type -AssemblyName System.IO.Compression.FileSystem
if (!(Test-Path -Path "C:\sbt" )) {
(new-object System.Net.WebClient).DownloadFile(
'https://github.com/sbt/sbt/releases/download/v1.3.10/sbt-1.3.10.zip',
'https://github.com/sbt/sbt/releases/download/v1.4.4/sbt-1.4.4.zip',
'C:\sbt-bin.zip'
)
[System.IO.Compression.ZipFile]::ExtractToDirectory("C:\sbt-bin.zip", "C:\sbt")
Expand Down
174 changes: 95 additions & 79 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ val junitInterface = "com.novocode" % "junit-interface" % "0.11"
val nativePlatform = settingKey[String]("The target platform")
val nativeArch = settingKey[String]("The target architecture")
val nativeArtifact = settingKey[Path]("The target artifact location")
val nativeBuild = taskKey[Path]("Build the native artifact")
val nativeCompiler = settingKey[String]("The compiler for native compilation")
val nativeCompileOptions = settingKey[Seq[String]]("The native compilation options")
val nativeIncludes = settingKey[Seq[String]]("The native include paths")
val buildDarwin = taskKey[Path]("Build mac native library")
val buildDarwin = taskKey[Path]("Build fat binary for x86_64 and arm64 on mac os")
val buildDarwinX86_64 = taskKey[Path]("Build mac native library for x86_64")
val buildDarwinArm64 = taskKey[Path]("Build mac native library for arm64")
val buildLinux = taskKey[Path]("Build linux native library")
val buildWin32 = taskKey[Path]("Build windows native library")

Expand All @@ -29,6 +32,21 @@ val platforms = Map(
"linux" -> s"lib$libShortName.so"
)

buildDarwin := {
if (!(buildDarwin / skip).value) {
val fatBinary =
(Compile / resourceDirectory).value.toPath / "darwin" / "x86_64" / platforms("darwin")
val x86 = buildDarwinX86_64.value.toString
val arm = buildDarwinArm64.value.toString
val logger = streams.value.log
scala.util.Try(eval(Seq("lipo", "-create", "-o", s"$fatBinary", x86, arm), logger))
fatBinary
} else (Compile / resourceDirectory).value.toPath / "darwin" / "x86_64" / platforms("darwin")
}
buildDarwin / skip := Option(System.getenv.get("CI")).fold(false)(_ => true)

buildDarwinArm64 / nativeArch := "arm64"

inThisBuild(
List(
version := "1.2.1-SNAPSHOT",
Expand All @@ -52,14 +70,7 @@ inThisBuild(
if (isSnapshot.value) Some("snapshots" at nexus + "content/repositories/snapshots")
else Some("releases" at nexus + "service/local/staging/deploy/maven2")
},
nativeArch := {
System.getProperty("os.arch") match {
case "amd64" =>
"x86_64"
case arch =>
arch
}
},
nativeArch := "x86_64",
nativeCompiler := "gcc",
nativeCompileOptions := "-shared" :: "-O2" :: "-Wall" :: "-Wextra" :: Nil,
nativePlatform := (System.getProperty("os.name").head.toLower match {
Expand All @@ -79,7 +90,8 @@ name := "ipcsocket"
libraryDependencies ++= Seq(jna, jnaPlatform, junitInterface % Test)
crossPaths := false
autoScalaLibrary := false
nativeLibrarySettings("darwin")
nativeLibrarySettings("darwinX86_64")
nativeLibrarySettings("darwinArm64")
nativeLibrarySettings("linux")
nativeLibrarySettings("win32")
if (!isWin) (buildWin32 / nativeCompiler := "x86_64-w64-mingw32-gcc") :: Nil else Nil
Expand All @@ -102,86 +114,90 @@ Global / javaHome := {

def nativeLibrarySettings(platform: String): Seq[Setting[_]] = {
val key = TaskKey[Path](s"build${platform.head.toUpper}${platform.tail}")
val shortPlatform = if (platform.startsWith("darwin")) "darwin" else platform
Def.settings(
key / nativeCompileOptions ++= (if (platform == "win32")
Seq(
"-D__WIN__",
"-lkernel32",
"-ladvapi32",
"-ffreestanding",
"-fdiagnostics-color=always",
)
else Nil),
key / nativeCompileOptions ++= (shortPlatform match {
case "win32" =>
Seq("-D__WIN__", "-lkernel32", "-ladvapi32", "-ffreestanding", "-fdiagnostics-color=always")
case "darwin" => Seq("-arch", (key / nativeArch).value)
case _ => Nil
}),
key / nativeArtifact := {
val name = platforms.get(platform).getOrElse(s"lib$libShortName.so")
(Compile / resourceDirectory).value.toPath / platform / nativeArch.value / name
val name = platforms.get(shortPlatform).getOrElse(s"lib$libShortName.so")
val resourceDir = (Compile / resourceDirectory).value.toPath
val targetDir = (Compile / target).value.toPath
val arch = (key / nativeArch).value
(if (shortPlatform == "darwin") targetDir else resourceDir) / platform / arch / name
},
key / fileInputs += {
val glob = if (platform == "win32") "*Win*.{c,h}" else "*Unix*.{c,h}"
baseDirectory.value.toGlob / "jni" / glob,
},
key / skip := isWin || ((ThisBuild / nativePlatform).value match {
case `platform` => false
case p => p != "win32"
case p => p != "win32" && !platform.startsWith(p)
}),
key := Def.taskDyn {
if ((key / skip).value) Def.task(Paths.get(""))
else
Def.task {
val artifact = (key / nativeArtifact).value
val inputs = key.inputFiles.collect {
case i if i.getFileName.toString.endsWith(".c") => i.toString
}
val options = (key / nativeCompileOptions).value
val compiler = (key / nativeCompiler).value
val logger = streams.value.log
val includes = (key / nativeIncludes).value
key / nativeBuild := {
val artifact = (key / nativeArtifact).value
val inputs = key.inputFiles.collect {
case i if i.getFileName.toString.endsWith(".c") => i.toString
}
val options = (key / nativeCompileOptions).value
val compiler = (key / nativeCompiler).value
val logger = streams.value.log
val includes = (key / nativeIncludes).value

if (key.inputFileChanges.hasChanges || !artifact.toFile.exists) {
Files.createDirectories(artifact.getParent)
val cmd = Seq(compiler, "-o", artifact.toString) ++ includes ++ options ++ inputs
logger.debug(s"Running compilation: ${cmd mkString " "}")
val proc = new java.lang.ProcessBuilder(cmd: _*).start()
val thread = new Thread() {
setDaemon(true)
start()
val is = proc.getInputStream
val es = proc.getErrorStream
val isOutput = new ArrayBuffer[Int]
val esOutput = new ArrayBuffer[Int]
def drain(stream: InputStream, buffer: ArrayBuffer[Int], isError: Boolean): Unit = {
while (stream.available > 0) {
stream.read match {
case 10 =>
val msg = new String(buffer.map(_.toByte).toArray)
buffer.clear()
if (isError) logger.error(msg) else logger.info(msg)
case c => buffer += c
}
}
}
def drain(): Unit = {
drain(is, isOutput, false)
drain(es, esOutput, true)
}
override def run(): Unit = {
while (proc.isAlive) {
drain()
Thread.sleep(10)
}
drain()
}
}
proc.waitFor(1, TimeUnit.MINUTES)
thread.join()
if (proc.exitValue != 0)
throw new IllegalStateException(
s"'${cmd mkString " "}' exited with ${proc.exitValue}"
)
}
artifact
}
}.value,
if (key.inputFileChanges.hasChanges || !artifact.toFile.exists) {
Files.createDirectories(artifact.getParent)
eval(Seq(compiler, "-o", artifact.toString) ++ includes ++ options ++ inputs, logger)
}
artifact
},
key := {
if ((key / skip).value) (key / nativeArtifact).value
else (key / nativeBuild).value
},
key := key.dependsOn(Compile / compile).value,
)
}

def eval(cmd: Seq[String], logger: Logger): Unit = {
logger.debug(s"Running compilation: ${cmd mkString " "}")
val proc = new java.lang.ProcessBuilder(cmd: _*).start()
val thread = new Thread() {
setDaemon(true)
start()
val is = proc.getInputStream
val es = proc.getErrorStream
val isOutput = new ArrayBuffer[Int]
val esOutput = new ArrayBuffer[Int]
def drain(stream: InputStream, buffer: ArrayBuffer[Int], isError: Boolean): Unit = {
while (stream.available > 0) {
stream.read match {
case 10 =>
val msg = new String(buffer.map(_.toByte).toArray)
buffer.clear()
if (isError) logger.error(msg) else logger.info(msg)
case c => buffer += c
}
}
}
def drain(): Unit = {
drain(is, isOutput, false)
drain(es, esOutput, true)
}
override def run(): Unit = {
while (proc.isAlive) {
drain()
Thread.sleep(10)
}
drain()
}
}
proc.waitFor(1, TimeUnit.MINUTES)
thread.join()
if (proc.exitValue != 0)
throw new IllegalStateException(
s"'${cmd mkString " "}' exited with ${proc.exitValue}"
)
}
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.3.12
sbt.version=1.4.4
12 changes: 5 additions & 7 deletions src/main/java/org/scalasbt/ipcsocket/NativeLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,12 @@ class NativeLoader {
private static final boolean isMac;
private static final boolean isLinux;
private static final boolean isWindows;
private static final String arch;

static {
final String os = System.getProperty("os.name", "").toLowerCase();
isMac = os.startsWith("mac");
isLinux = os.startsWith("linux");
isWindows = os.startsWith("windows");
String maybeArch = System.getProperty("os.arch", "");
if ("amd64".equals(maybeArch)) {
maybeArch = "x86_64";
}
arch = maybeArch;
}

private static final String pid =
Expand All @@ -46,14 +40,18 @@ private static String tmpDirLocation() {

static void load() throws UnsatisfiedLinkError {
if (!loaded.get()) {
final String os = System.getProperty("os.name", "").toLowerCase();
final boolean isMac = os.startsWith("mac");
final boolean isLinux = os.startsWith("linux");
final boolean isWindows = os.startsWith("windows");
final boolean is64bit = System.getProperty("sun.arch.data.model", "64").equals("64");
String tmpDir = tmpDirLocation();
if (is64bit && (isMac || isLinux || isWindows)) {
final String extension = "." + (isMac ? "dylib" : isWindows ? "dll" : "so");
final String libName = (isWindows ? "" : "lib") + "sbtipcsocket" + extension;
final String prefix = isMac ? "darwin" : isLinux ? "linux" : "win32";

final String resource = prefix + "/" + arch + "/" + libName;
final String resource = prefix + "/x86_64/" + libName;
final URL url = NativeLoader.class.getClassLoader().getResource(resource);
if (url == null) throw new UnsatisfiedLinkError(resource + " not found on classpath");
try {
Expand Down
Binary file not shown.
Binary file modified src/main/resources/darwin/x86_64/libsbtipcsocket.dylib
Binary file not shown.
Binary file modified src/main/resources/win32/x86_64/sbtipcsocket.dll
Binary file not shown.