使用homebrew安装jsc

安装方式

结论优先,如果你并不关心具体的实现细节,只想问我到底应该怎么快速安装上jsc,并且能够使用,那么跟着我,左手,右手,一个慢动作:打开terminal,输入下面这一行命令。

# Homebrew must be installed 
==> brew install mapleeit/tap/jsc

好了,你现在可以像在windows下一样使用jsc了。:)
什么,你不信?能有这么简单?试一试又不会怀孕。

前言

最近想把开发环境切到mac下面来,因为个人对于mac这套工作流要比windows下面更为熟悉。其中一个环节就是把jsc切到mac下面来,其实方法很简单,就是把jsc中的代码clone下来,然后把jsc/bin/jsc.sh配置到$PATH中,具体方法@yuanzong已经在文章汇总介绍,其中bashzsh的添加环境变量方式不同。 这种方案有两种缺点:

  • 需要手动配置。2016年了,我们不能自动化一点么。
  • 环境变量不方便管理。万一未来不用这一套工作流了,这种环境变量肯定会忘记修改过来,久而久之,电脑中垃圾东西会越来越多,我是个精神洁癖重度患者,这种事情简直要命。
  • 项目文件不方便管理。原有的jsc项目文件还要找个地方一直放着,也

作为一个mac下做开发的程序员,怎么能让这种不优雅的方式存在于我们的工作流搭建中呢?
什么方案是比较优雅的呢? 相信所有mac程序员对homebrew这款产品都非常熟悉,它非常方便我们对command line application进行管理,安装的时候只要brew install APP就可以了,卸载时候也非常方便brew uninstall APP。当然前提是homebrew仓库中收录了你所需要的app获取安装路径和安装方式。
显而易见,对于我们公司内部团队使用的一种打包方式jschomebrew仓库中并没有保存,这个时候需要我们自己来制作一个jschomebrewFormula

什么是Formula

来来来,先了解几个homebrew中的术语。

Term Description Example
Formula The package definition /usr/local/Library/Taps/homebrew/homebrew-core/Formula/foo.rb
Keg The installation prefix of a Formula /usr/local/Cellar/foo/0.1
opt prefix A symlink to the active version of a Keg /usr/local/opt/foo
Cellar All Kegs are installed here /usr/local/Cellar
Tap An optional Git repository of Formulae and/or commands usr/local/Library/Taps/homebrew/homebrew-versions
Bottle Pre-built Keg used instead of building from source qt-4.8.4mavericks.bottle.tar.gz

具体过程

总体思路

它的原理就是利用ruby脚本来将你选择的包从github上面下载下来,然后用它做好的函数框架来写Formulae,在其中制定你自己的安装方式。

这里确定它到底做了什么可以在install过程打出如下命令:

brew install --debug --verbose APPNAME

总结下来主要干了这么两件事:

  • 把包安装在/usr/local/Cellar/中,Formulae可以确定:

    • github repo address
    • 项目版本(从而实现版本管理)
    • 安装范围(确定需要把git repo中哪些文件来安装到/usr/local/Cellar/中)
  • 然后将/usr/local/Cellar/APPNAME/VERSION/bin/APP.whatever建立软连接ln -s/usr/local/bin中。(这个其实就是添加了$PATH过程)

1.建立jsc项目的github repo

一位伟人说过:要生孩子,首先要有个女朋友。

首先,你要有个repo,我们上面提到有现成的jsc github repo,那么我们只需要fork自己的账号

2.建立自己的Formulae

比如你现在已经fork了我的github repo,有了自己的仓库地址https://github.com/USERNAME/jsc
接下来你在本地找个地方把这个项目给clone下来:

git clone https://github.com/USERNAME/jsc

然后,如果不需要做修改,你只需要把这个项目在github上面做一下发布。

git tag 1.0.0
git push --tag

可能一般的人没有用过发布功能,其实就是把你的项目打包为ziptar.gz方便别人下载使用。
然后你打开你github中的jsc项目,发现release下面是有这种版本追踪的流水图。

然后复制下来tar.gz的链接地址,例如这样的https://github.com/mapleeit/jsc/archive/2.0.0.tar.gz,等下要用。
然后在Terminal中打出如下命令:

brew create https://github.com/mapleeit/jsc/archive/2.0.0.tar.gz

这个命令会在/usr/local/Library/Taps/homebrew/homebrew-core/Formula/这个目录下面生成一个foo.rb这样的ruby程序,这个就是传说中的Formula,我们来看一下它里面长什么样子吧。

# Documentation: https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Formula-Cookbook.md
#                /usr/local/Library/Contributions/example-formula.rb
# PLEASE REMOVE ALL GENERATED COMMENTS BEFORE SUBMITTING YOUR PULL REQUEST!

class Formalfridayclub < Formula
  homepage ""
  url "https://github.com/mapleeit/jsc/archive/1.0.0.tar.gz"
  version ""
  sha1 ""

  # depends_on "cmake" => :build
  depends_on :x11 # if your formula requires any X11/XQuartz components

  def install
    # ENV.deparallelize  # if your formula fails when building in parallel

    # Remove unrecognized options if warned by configure
    system "./configure", "--disable-debug",
                          "--disable-dependency-tracking",
                          "--disable-silent-rules",
                          "--prefix=#{prefix}"
    # system "cmake", ".", *std_cmake_args
    system "make", "install" # if this fails, try separate make/make install steps
  end

  test do
    # `test do` will create, run in and delete a temporary directory.
    #
    # This test will fail and we won't accept that! It's enough to just replace
    # "false" with the main program this formula installs, but it'd be nice if you
    # were more thorough. Run the test with `brew test formalfridayclub`. Options passed
    # to `brew install` such as `--HEAD` also need to be provided to `brew test`.
    #
    # The installed folder is not in the path, so use the entire path to any
    # executables being tested: `system "#{bin}/program", "do", "something"`.
    system "false"
  end
end

对于我们这次简单的Formula来说,其他的都是废话,精髓就是

class Formalfridayclub < Formula
  homepage ""
  url "https://github.com/mapleeit/jsc/archive/1.0.0.tar.gz"
  version "1.0.0"
  sha1 ""
  def install 
    # INSTALL STATMENTS WRITE HERE
  end 
end

什么,还是太复杂,那我们就只看这句话

  def install
    # INSTALL STATMENTS WRITE HERE
  end

其实,对于我们这种没有任何依赖,不需要任何构建的小项目来说,这句话就是全世界了。那么问题来了,这里面怎么写呢?
homebrew给我们定义了一些接口。
比如最常用的就是bin.install "foo",这句话什么意思呢,就是这个意思:

mv PATH/foo /usr/local/Cellar/jsc/1.0.0/bin/foo
chmod 0555 /usr/local/Cellar/jsc/1.0.0/bin/foo # 让该script可执行

你如果只有一个脚本文件jsc的话,那好说,直接在INSTALL STATMENTS那里敲上bin.install "jsc"就搞定了。但是显然现实是有些残酷的,我们的项目长这个样子:

这里面不是仅仅只有一个脚本文件,脚本文件只是一个入口而已,后面有大量node写的文件监控和合并服务。那怎么把这些自己写的文件也装进去呢?

  def install 
    predix.install Dir["jsc/*"]
  end

你一定想问,这个bin.installprefix.install都是什么鬼?你看看这个就知道了。Just moving some files

这句话就是把所有jsc下的文件一股脑都装到/usr/local/Cellar/jsc/VERSION/下面,我们来看一下效果:


发现东西确实都放在了这里,那么这个过程解决了一个痛点:再也不用操心项目文件存放的事情了
那么我们来看一下$PATH也就是/usr/local/bin中现在是什么样子的呢?


为什么会多出来这么多文件?我们现在来看下/usr/local/Cellar/jsc/2.0.0/bin下面都有什么:

发现了它把所有的/usr/local/Cellar/jsc/VERSION/bin目录下的文件都建立了软连接到/usr/local/bin下面,这样就解决了第二个痛点:再也不用操心环境变量管理的问题了

这就完了么?

就这么简单么,这不是一会就搞定了,你还值得写一篇文章来记录?另外一位伟人说过:不填坑的文章都是耍流氓。

坑一

年少的我以为这个非常简单,当发现bin.install的时候,我以为我理解了全世界,当时我的Formula是这样写的:

  def install
    bin.install "jsc/bin/jsc"
  end

然后发现非常棒,跟你想的一样,它实现了这样的功能:

  1. /usr/local/Cellar/jsc/VERSION/中添加了bin/jsc

  1. /usr/local/bin中建立了软连接


然而,当我兴奋地在命令行中敲下jsc的时候,现实给了无情的报错:


大概看了一眼报错,知道是没有引到某个文件。
这时候注意,我们Cellar/jsc/文件中目录是这样的:

我们在jsc这个sh程序中是怎么写的呢?

#!/bin/sh
CUR_DIR=$(cd $(dirname $0);pwd)
CUR_PATH=$(pwd)

node '$CUR_DIR/../../index.js' -m '$CUR_PATH/'

好吧,原来你是需要依赖到项目中的其他文件,而bin.install只安装了其中的入口脚本,那我就把其他文件搞过来就好了。
于是找啊找,发现了上面所说的prefix.install Dir["jsc/*"],于是我满怀信心地把它们都修改好了。可以看到修改之后的Cellar/jsc/中文件目录是这样的:

坑二

可以发现所有的东西其实已经都安装上去了,这时候再回到测试目录尝试一下:


WTF???
为什么还是这个报错,这个时候我仔细看了一下报错,发现引用路径是不对的。这时候sh执行的时候,是去找了/usr/index.js,为什么会找到这个路径呢?我们看这行代码

node '$CUR_DIR/../../index.js' -m '$CUR_PATH/'

发现这时候$CUR_DIR指向的是/usr/local/bin这个目录,因为我们在全局状态下使用jsc命令,其实都是在执行/usr/local/bin之中的jsc脚本,所以按照

CUR_DIR=$(cd $(dirname $0);pwd)

这种方法取到的路径并不是文件存在真实的路径,只是建立的软连接的路径。
那么把CUR_DIR这个变量指向脚本的真实路径就可以了,那google一下咯,应该很好实现吧?不出所料,果然有现成的命令可以实现取到软连接的真实路径readlink
那么,我们修改一下我们的jsc脚本:

#!/bin/sh
myPATH=/usr/local/bin
CUR_DIR=$(readlink $myPATH/jsc)
CUR_PATH=$(pwd)

node "$myPATH/$CUR_DIR/../../index.js" m="$CUR_PATH/"

这下应该没问题了,我们安装一下。
brew install mapleeit/tap/jsc
然后来到测试文件夹测试一下


成功搞出来了index.js,看下里面,发现没有问题。

It WORKED!

周末就这样没了。

参考文献