Changeset 5f81a8de for launchers


Ignore:
Timestamp:
May 5, 2018 11:34:35 PM (2 years ago)
Author:
meeh <meeh@…>
Branches:
master
Children:
0a1191a
Parents:
f6273a1
Message:

Mac OS X Launcher - reborn - ALPHA!

TLDR;
Howto? ant osxLauncher
Privacy Notes? If you don't got SBT, a bash script will trigger

download of SBT for you with task osxLauncher.

Results? open ./launchers/output
"Binary" App Bundle name: I2P.app
Runtime base directory? ~/Library/I2P
Runtime config directory? untouched.

After talk on IRC with zzz, I rewrote the logic since we could
start with a simple deploy, for a faster alpha version ready :)

SBT will build a zip file from the content of pkg-temp, which
CompleteDeployment?.scala will again unzip in runtime. Right now
it's quite basic, but the plan is to add version detection, so
it's capable of upgrading a already deployed I2P base directory.

OSXDeployment.scala is renamed to PartialDeployment?.scala for usage
in the browser bundle launcher, since it's going to be a subset of
the files found in pkg-temp.

A Info.plist is added to the launchers/macosx which is added to the
application bundle under building. Note that this differ from the one
in Start i2p router.app that's been here for years now.

Location:
launchers
Files:
3 added
4 edited
1 moved

Legend:

Unmodified
Added
Removed
  • launchers/common/src/main/scala/net/i2p/launchers/PartialDeployment.scala

    rf6273a1 r5f81a8de  
    1313/**
    1414  *
    15   * OSXDeployment
     15  * NOTE: Work in progress: Originally written for OSX launcher - but will be used in BB launcher.
     16  *
     17  * PartialXDeployment
    1618  *
    1719  * This class can be a bit new for java developers. In Scala, when inherit other classes,
     
    7779  * @since 0.9.35
    7880  */
    79 class OSXDeployment extends
     81class PartialDeployment extends
    8082  DeployProfile(
    8183    OSXDefaults.getOSXConfigDirectory.getAbsolutePath,
  • launchers/common/src/main/scala/net/i2p/launchers/RouterLauncher.scala

    rf6273a1 r5f81a8de  
    22
    33import java.io.File
     4
     5import scala.concurrent.Future
     6import scala.sys.process.Process
    47
    58
     
    1114  */
    1215abstract class RouterLauncher {
    13   def runRouter(basePath: File, args: Array[String]): Unit
     16  def runRouter(basePath: File, args: Array[String]): Future[Process]
    1417
    15   def runRouter(args: Array[String]): Unit
     18  def runRouter(args: Array[String]): Future[Process]
    1619}
  • launchers/macosx/build.sbt

    rf6273a1 r5f81a8de  
    1 import sbtassembly.AssemblyPlugin.defaultShellScript
    2 import sbt._
    3 import Keys._
    4 import sbt.io.IO
    5 import java.io.File
     1import java.io.{File, FileNotFoundException, FileOutputStream}
     2import java.util.zip._
    63
    74lazy val i2pVersion = "0.9.34"
     
    96lazy val cleanAllTask = taskKey[Unit]("Clean up and remove the OSX bundle")
    107lazy val buildAppBundleTask = taskKey[Unit](s"Build an Mac OS X bundle for I2P ${i2pVersion}.")
    11 lazy val bundleBuildPath = file("./output")
     8lazy val buildDeployZipTask = taskKey[String](s"Build an zipfile with base directory for I2P ${i2pVersion}.")
     9lazy val bundleBuildPath = new File("./output")
    1210
    1311lazy val staticFiles = List(
     
    3331    "#!/usr/bin/env sh",
    3432    s"""
    35        |echo "Yo"
     33       |echo "I2P - Mac OS X Launcher starting up"
    3634       |export I2P=$$HOME/Library/I2P
    3735       |for jar in `ls $${I2P}/lib/*.jar`; do
     
    6765      "-Dwrapper.java.pidfile=/tmp/routerjvm.pid",
    6866      "-Dwrapper.console.loglevel=DEBUG",
    69       "-Djava.awt.headless=true",
    7067      "-Di2p.dir.base=$I2P",
    7168      "-Djava.library.path=$I2P"
     
    9390}
    9491
     92buildDeployZipTask := {
     93  println(s"Starting the zip file build process. This might take a while..")
     94  if (!bundleBuildPath.exists()) bundleBuildPath.mkdir()
     95  val sourceDir = i2pBuildDir
     96  def recursiveListFiles(f: File): Array[File] = {
     97    val these = f.listFiles
     98    these ++ these.filter { f => f.isDirectory }.flatMap(recursiveListFiles).filter(!_.isDirectory)
     99  }
     100  def zip(out: String, files: Iterable[String]) = {
     101    import java.io.{ BufferedInputStream, FileInputStream, FileOutputStream }
     102    import java.util.zip.{ ZipEntry, ZipOutputStream }
     103
     104    val zip = new ZipOutputStream(new FileOutputStream(out))
     105
     106    files.foreach { name =>
     107      val fname = sourceDir.toURI.relativize(new File(name).toURI).toString
     108      //println(s"Zipping ${fname}")
     109      if (!new File(name).isDirectory) {
     110        zip.putNextEntry(new ZipEntry(fname))
     111        val in = new BufferedInputStream(new FileInputStream(name))
     112        var b = in.read()
     113        while (b > -1) {
     114          zip.write(b)
     115          b = in.read()
     116        }
     117        in.close()
     118        zip.closeEntry()
     119      }
     120    }
     121    zip.close()
     122  }
     123  val fileList = recursiveListFiles(sourceDir.getCanonicalFile).toList
     124  val zipFileName = new File(bundleBuildPath, "i2pbase.zip").getCanonicalPath
     125  zip(zipFileName, fileList.map { f => f.toString }.toIterable)
     126  zipFileName.toString
     127}
     128
    95129buildAppBundleTask := {
    96130  println(s"Building Mac OS X bundle for I2P version ${i2pVersion}.")
    97   bundleBuildPath.mkdir()
     131  if (!bundleBuildPath.exists()) bundleBuildPath.mkdir()
    98132  val paths = Map[String,File](
    99133    "execBundlePath" -> new File(bundleBuildPath, "I2P.app/Contents/MacOS"),
    100     "resBundlePath" -> new File(bundleBuildPath, "I2P.app/Contents/Resources"),
    101     "i2pbaseBunldePath" -> new File(bundleBuildPath, "I2P.app/Contents/Resources/i2pbase"),
    102     "i2pJarsBunldePath" -> new File(bundleBuildPath, "I2P.app/Contents/Resources/i2pbase/lib"),
    103     "webappsBunldePath" -> new File(bundleBuildPath, "I2P.app/Contents/Resources/i2pbase/webapps")
     134    "resBundlePath" -> new File(bundleBuildPath, "I2P.app/Contents/Resources")
    104135  )
    105136  paths.map { case (s,p) => p.mkdirs() }
    106   val dirsToCopy = List("certificates","locale","man")
    107137
    108138  val launcherBinary = Some(assembly.value)
     
    110140
    111141
    112   /**
    113     *
    114     * First of, if "map" is unknown for you - shame on you :p
    115     *
    116     * It's a loop basically where it loops through a list/array
    117     * with the current indexed item as subject.
    118     *
    119     * The code bellow takes the different lists and
    120     * copy all the directories or files from the i2p.i2p build dir,
    121     * and into the bundle so the launcher will know where to find i2p.
    122     *
    123     */
    124   dirsToCopy.map  { d => IO.copyDirectory( new File(resDir, d), new File(paths.get("i2pbaseBunldePath").get, d) ) }
    125   warsForCopy.map { w => IO.copyFile( new File(new File(i2pBuildDir, "webapps"), w), new File(paths.get("webappsBunldePath").get, w) ) }
    126   jarsForCopy.map { j => IO.copyFile( new File(new File(i2pBuildDir, "lib"), j), new File(paths.get("i2pJarsBunldePath").get, j) ) }
     142  val plistFile = new File("./macosx/Info.plist")
     143  if (plistFile.exists()) {
     144    println(s"Adding Info.plist...")
     145    IO.copyFile(plistFile, new File(bundleBuildPath, "I2P.app/Contents/Info.plist"))
     146  }
     147
     148  val zipFilePath = Some(buildDeployZipTask.value)
     149
     150  val zipFileOrigin = new File(zipFilePath.get)
     151  IO.copyFile(zipFileOrigin, new File(paths.get("resBundlePath").get, "i2pbase.zip"))
     152  println(s"Zip placed into bundle :)")
     153
    127154}
  • launchers/macosx/src/main/scala/net/i2p/launchers/osx/LauncherAppMain.scala

    rf6273a1 r5f81a8de  
    11package net.i2p.launchers.osx
    22
     3import java.awt.SystemTray
    34import java.io.File
    45
    5 import net.i2p.launchers.{DeployProfile, OSXDefaults, OSXDeployment}
     6import net.i2p.launchers.{CompleteDeployment, OSXDefaults}
     7
     8import scala.concurrent.{Await, Future}
     9import scala.concurrent.ExecutionContext.Implicits.global
     10import scala.concurrent.duration._
     11import scala.sys.process.Process
     12import scala.util.{Failure, Success}
    613
    714/**
     
    3744  val i2pBaseDir = OSXDefaults.getOSXBaseDirectory
    3845
    39   new OSXDeployment()
     46  val selfDirPath = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath).getParentFile
    4047
     48  // Tricky to get around, but feels hard to use a "var" which means mutable..
     49  // It's like cursing in the church... Worse.
     50  var sysTray: Option[SystemTrayManager] = None
     51
     52  val deployment = new CompleteDeployment(new File(selfDirPath.getPath, "../Resources/i2pbase.zip"), i2pBaseDir)
     53
     54  val depProc = deployment.makeDeployment
    4155  // Change directory to base dir
    4256  System.setProperty("user.dir", i2pBaseDir.getAbsolutePath)
    4357
     58  // System shutdown hook
     59  sys.ShutdownHookThread {
     60    println("exiting launcher process")
     61  }
     62
     63  Await.ready(depProc, 60000 millis)
     64
     65  println("I2P Base Directory Extracted.")
     66
    4467  try {
    45     MacOSXRouterLauncher.runRouter(i2pBaseDir, args)
     68    val routerProcess: Future[Process] = MacOSXRouterLauncher.runRouter(i2pBaseDir, args)
     69
     70    if (SystemTray.isSupported) {
     71      sysTray = Some(new SystemTrayManager)
     72    }
     73
     74    routerProcess onComplete {
     75      case Success(forkResult) => {
     76        println(s"Router started successfully!")
     77        try {
     78          val routerPID = MacOSXRouterLauncher.pid(forkResult)
     79          println(s"PID is ${routerPID}")
     80        } catch {
     81          case ex:java.lang.RuntimeException => println(s"Minor error: ${ex.getMessage}")
     82        }
     83        if (!sysTray.isEmpty) sysTray.get.setRunning(true)
     84      }
     85      case Failure(fail) => {
     86        println(s"Router failed to start, error is: ${fail.toString}")
     87      }
     88    }
     89
     90    //Await.result(routerProcess, 5000 millis)
     91
    4692  } finally {
    47     System.out.println("Exit.")
     93    System.out.println("Exit?")
    4894  }
    4995}
  • launchers/macosx/src/main/scala/net/i2p/launchers/osx/MacOSXRouterLauncher.scala

    rf6273a1 r5f81a8de  
    22
    33import java.io.File
     4import java.lang.reflect.Field
    45
    56import scala.sys.process.Process
    67import net.i2p.launchers.RouterLauncher
     8
     9import scala.concurrent.Future
     10import scala.concurrent.ExecutionContext.Implicits.global
    711
    812/**
     
    1418object MacOSXRouterLauncher extends RouterLauncher {
    1519
    16   override def runRouter(args: Array[String]): Unit = {}
     20  def pid(p: Process): Long = {
     21    val procField = p.getClass.getDeclaredField("p")
     22    procField.synchronized {
     23      procField.setAccessible(true)
     24      val proc = procField.get(p)
     25      try {
     26        proc match {
     27          case unixProc
     28            if unixProc.getClass.getName == "java.lang.UNIXProcess" => {
     29            val pidField = unixProc.getClass.getDeclaredField("pid")
     30            pidField.synchronized {
     31              pidField.setAccessible(true)
     32              try {
     33                pidField.getLong(unixProc)
     34              } finally {
     35                pidField.setAccessible(false)
     36              }
     37            }
     38          }
     39          case procImpl:java.lang.Process => {
     40            val f: Field = p.getClass().getDeclaredField("p")
     41            val f2: Field = f.get(p).getClass.getDeclaredField("pid")
     42            try {
     43              f.setAccessible(true)
     44              f2.setAccessible(true)
     45              val pid = f2.getLong(p)
     46              pid
     47            } finally {
     48              f2.setAccessible(false)
     49              f.setAccessible(false)
     50            }
     51          }
     52          // If someone wants to add support for Windows processes,
     53          // this would be the right place to do it:
     54          case _ => throw new RuntimeException(
     55            "Cannot get PID of a " + proc.getClass.getName)
     56        }
     57      } finally {
     58        procField.setAccessible(false)
     59      }
     60    }
     61  }
    1762
    18   def runRouter(basePath: File, args: Array[String]): Unit = {
    19     lazy val javaOpts = Seq(
    20       "-Xmx512M",
    21       "-Xms128m",
    22       "-Dwrapper.logfile=/tmp/router.log",
    23       "-Dwrapper.logfile.loglevel=DEBUG",
    24       "-Dwrapper.java.pidfile=/tmp/routerjvm.pid",
    25       "-Dwrapper.console.loglevel=DEBUG",
    26       s"-Di2p.dir.base=${basePath}",
    27       s"-Djava.library.path=${basePath}"
    28     )
    29     val javaOptsString = javaOpts.map(_ + " ").mkString
    30     val cli = s"""java -cp "${new File(basePath, "lib").listFiles().map{f => f.toPath.toString.concat(":")}.mkString}." ${javaOptsString} net.i2p.router.Router"""
    31     println(s"CLI => ${cli}")
    32     val pb = Process(cli)
    33     // Use "run" to let it fork in behind
    34     val exitCode = pb.!
     63
     64  // ??? equals "throw not implemented" IIRC - it compiles at least :)
     65  override def runRouter(args: Array[String]): Future[Process] = ???
     66
     67  def runRouter(basePath: File, args: Array[String]): Future[Process] = {
     68    Future {
     69      lazy val javaOpts = Seq(
     70        "-Xmx512M",
     71        "-Xms128m",
     72        "-Djava.awt.headless=true",
     73        "-Dwrapper.logfile=/tmp/router.log",
     74        "-Dwrapper.logfile.loglevel=DEBUG",
     75        "-Dwrapper.java.pidfile=/tmp/routerjvm.pid",
     76        "-Dwrapper.console.loglevel=DEBUG",
     77        s"-Di2p.dir.base=${basePath}",
     78        s"-Djava.library.path=${basePath}"
     79      )
     80      val javaOptsString = javaOpts.map(_ + " ").mkString
     81      val cli = s"""java -cp "${new File(basePath, "lib").listFiles().map{f => f.toPath.toString.concat(":")}.mkString}." ${javaOptsString} net.i2p.router.Router"""
     82      println(s"CLI => ${cli}")
     83      val pb = Process(cli)
     84      // Use "run" to let it fork in behind
     85      pb.run
     86    }
    3587  }
    3688}
Note: See TracChangeset for help on using the changeset viewer.