基于MainFramer进行远程编译(以Android开发为例)

一、故事背景(可略过)

以前,世界上只有两种电脑:笔记本和台式机。

上帝说,要有高性能的笔记本。于是,就有了MainFramer。

我就遇到了这种情况,上班后公司配的是MacBookPro的笔记本和一个Windows的台式机(PC),我曾尝试过使用MacBook作为主力机工作,MacOS的体验非常优秀,但是唯一的不足就是其在处理大型复杂任务时的速度远不如我那台高配的台式机,编译一次工程的时间差在3分钟左右,而我每天编译上20次就能差出一个小时,有这个时间提前让我下班多好。最重要的是,我只要一点击Build,整个机器就开始满负荷运行,除了温度飙升外,CPU也被占满,这时候开个网页都卡成了幻灯片。

为了提高工作效率,我尝试一直用台式机工作,于是我的MBP吃灰了很久,这么优秀的笔记本放在那里吃灰实在是心疼。最关键的是,台式机让我完全没有工作流的感觉,当我周末无聊想充电的时候,我发现我所有想看的东西都在公司,当我一个个软件打开,一个个网页从历史记录中找到后,我发现我已经没有了学习的欲望。这时候我非常想念用笔记本时那种“合盖走人”后,回家打开盖子发现我所有的思路都还在的感觉。

于是我急切的找一种方法,让简单的任务在笔记本上来做,复杂的任务能够利用台式机完成,并且在体验上做到“无差别”。终于,我在浏览全球最大的同性社交网站时惊喜的发现了mainframer,这是一个工具,可以将编译这件占内存和CPU的事从本地电脑放到远程电脑上来做。

简单的说,你只需要一台本地机器(Local Machine:例如我的性能不咋地的MacBook)和一台远程机器(Remote Machine:性能强悍的台式机或者云主机),通过mainframer,就可以实现在本地机器上写代码,在远程机器上编译代码。你可以在笔记本上写代码,到了需要调试的时候,mainframer会快速同步代码到远程机器并进行编译,并将编译的结果返回到你的笔记本,这一切仿佛都是在你的笔记本上进行的,你可以正常的build和调试程序。这样一来,就可以享受笔记本的便捷,又能享受台式机的高性能了,美滋滋。

下面就是我配置成功后,录制的在MacBookPro上写代码,在Linux发行版Manjaro上进行Build的视频:

传送门

目前该工具支持所有使用以下技术构建的代码:

二、开始安装和配置

mainframer的安装非常简单,以Android为例,你只需要:

  1. 下载mainframer到你工程的根目录下;
  2. 配置本地电脑和远程电脑的SSH;
  3. 根据不通类型的代码配置一下开发环境;

可以根据github上的readmen一步步的操作,有英文不好的小伙伴可以参考我实践的路子:

1.下载mainframer

这里下载mainframer的sh文件,放到Android工程的根目录下就可以了

2. 配置本地电脑和远程电脑的SSH

2.1 配置SSH的目的是为了能让本地电脑直接运行ssh+远程电脑名字就可以进行ssh连接,避免中间复杂的输密码等过程,在配置前,你需要先了解远程电脑的三个参数:

  • REMOTE_MACHINE_ALIAS — 远程电脑的名字,通常在Linux中是hostname,可以通过cat /etc/hostname查看,在mac上可以通过scutil --get ComputerName查看.
  • REMOTE_MACHINE_IP_OR_HOSTNAME — 远程电脑的IP或者域名,如果是局域网内的电脑,则可能是192.168.XX.XX的形式,如果是云服务器可以直接填服务器地址如 42.42.42.42 or remote.domain.com.
  • REMOTE_MACHINE_USERNAME — 远程电脑的用户名,这里官方建议在远程电脑新建一个用户(下面有脚本)来操作,这里可以现在就起一个名字,例如calvin。

后续教程中出现上面三个参数时,请替换为其正确的值。

2.2 知道了这三个参数,就可以开始配置了,首先配置本地电脑:

  • 在本地生成ssh密钥:
1
ssh-keygen -t rsa -b 4096 -C "{REMOTE_MACHINE_USERNAME}"  //{REMOTE_MACHINE_USERNAME}替换为刚才起的用户名calvin

一路按回车就可以在~/.ssh/目录下生成公钥文件id_rsa.pub和私钥文件id_rsa.

  • 配置本地SSH

开启SSH的ControlMaster并持久化socket连接,可以避免每次连接远程机器时重新建立连接,并可以省掉输入密码的过程,加速SSH命令的执行速度。具体操作为,打开~/.ssh/config(如果没有则创建一个新的),并在里面输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Host {REMOTE_MACHINE_ALIAS}
User {REMOTE_MACHINE_USERNAME}
HostName {REMOTE_MACHINE_IP_OR_HOSTNAME}
Port 22
IdentityFile ~/.ssh/{SSH_KEY_NAME}
PreferredAuthentications publickey
ControlMaster auto
ControlPath /tmp/%r@%h:%p
ControlPersist 1h

# 例如
Host calvin
User calvinche
HostName 192.168.31.129
Port 22
IdentityFile ~/.ssh/id_rsa
PreferredAuthentications publickey
ControlMaster auto
ControlPath /tmp/%r@%h:%p
ControlPersist 1h

其中,{REMOTE_MACHINE_ALIAS}替换为远程机器的名字,{REMOTE_MACHINE_USERNAME}替换为远程机器的用户名,{REMOTE_MACHINE_IP_OR_HOSTNAME}替换为远程机器的IP或域名,{SSH_KEY_NAME}替换为上一节说的在本地生成ssh私钥文件的名字,其位置位于~/.ssh/下,名字一般为id_rsa。

2.3 下面登陆远程机器,进行配置:

在远程电脑上创建新用户{REMOTE_MACHINE_USERNAME},并将配置公钥,这里可以直接通过官方脚本进行操作,将脚本remote_machine_setup.sh下载到远程服务器并这样运行:

1
remote_machine_setup.sh {REMOTE_MACHINE_USERNAME} "引号内将刚才生成的公钥文件中的内容复制过来"

这里的REMOTE_MACHINE_USERNAME就是在远程机器上新建的用户,这样如果几个人共用一台远程服务器来写一个工程就不会讲代码混淆在一起了。

执行后若显示New user $NEW_USER was set up correctly就说明创建成功了。

通过输入 ssh 远程机器的用户名 如ssh calvin来验证是否能够顺利登陆到远程机器,如果不需要输密码就能登陆成功,那么就可以了,如果出现:Permission denied (publickey,password)这样的话就说明远程机器的公钥与本地不符,或者本地ssh的config文件中私钥的名字不正确,需要认真检查。

到这里,基本上就完成了mainframer的配置工作,可以通过运行下面代码测试能否工作:

1
bash ./mainframer.sh echo "I am Calvin" > success.txt

如果本地的success文件中的内容如下图所示,那么整个流程就跑通了。

如果您是Android开发的话,可以通过命令 ./mainframer ./gradlew build 进行远程编译了。

如果你还想再进一步,把该工具添加到开发环境中,实现无缝隙的远程编译,可以根据背景一节中列出的不同的开发的链接查看教程,下面以Android为例,介绍怎么将其接入到Android Studio中,实现,点击调试按钮,自动远程编译。

3. 根据不通类型的代码配置开发环境(以Android为例)

打开Android Studio, 按照下面步骤进行配置:

  • 点击RunEdit Configuration+.
  • 选择 Android App.
  • 起一个好一点的名字, 例如remote-build.
  • 在Module中选择要编译的模块名字,如app.
  • 在Before Launch只点击减号删除原来的 Gradle-aware Make
  • 在Before Launch只点击+号创建一个 Run External Tool.
  • 填个好一点的名字,如remote assembleDebug.
  • Program里填bash.
  • Parameters里填mainframer.sh ./gradlew :app:assembleDebug -Pandroid.enableBuildCache=true
  • Working directory里填$ProjectFileDir$.

示意图:

整个操作就完成了,可以愉快的玩耍了。很开心,是不是?

三、填坑

坑No.1 环境变量问题

第二节中的安装方法是根据mainframer的github仓库Readme进行的尝试,事实上安装和配置完成后也不是那么一帆风顺,点击编译后的遇到一个找不到SDK位置问题:

1
2
3
* What went wrong:
A problem occurred configuring project ':app'.
> SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable.

但是我在远程机器中添加了ANDROID_HOME变量,如下,但还是报错

1
2
echo $ANDROID_HOME
/opt/android-sdk

经过一轮搜索发现,原来mainframe使用的ssh登陆的shell是non-interactive的,在这种模式下,无法读取你定义在~/.bashrc /etc/bashrc /etc/profile等文件中的变量

最终,有两种方法可以解决找不到环境变量的问题:

第一种,配置~/.ssh/environment方法:

你需要通过下面方式,配置SDK的位置和JAVA的位置:

  1. 首先,打开远程机器的SSHD配置文件,运行:sudo vim /etc/ssh/sshd_config.在打开的文件中查找这一行# PermitUserEnvironment no,并将其改成PermitUserEnvironment=yes
  2. 然后在~/.ssh目录下创建environment文件:vim ~/.ssh/environment,并将变量以key=value的形式写入,例如我的配置文件为:
1
2
ANDROID_HOME=/home/calvinche/Android/Sdk
JAVA_HOME=/home/calvinche/Android/jdk1.8.0_181/

这样,本地机器通过ssh登陆后就能正确找到sdk和java的位置了。

不过该方法不能像Shell一样用path=path:/home/...这种语法,所以只能访问到你配置的这几个有限的地址,很多命令还是没法使用。如果你的gradle脚本中使用了jar命令,那么还会提醒你找不到文件,所以建议使用下面的方法

第二种,加载完整的远程环境变量:

在上一节的第3步中(3. 根据不通类型的代码配置开发环境(以Android为例)),在Parameters中运行的自定义命令前,加载一下远程机器的环境配置文件,命令如下:

source /etc/profile

例如我的配置为:

mainframer.sh "source /etc/profile && bash ./gradlew :app:assembleLocal_Debug --stacktrace"

坑No.2 Before launch中总是自动添加Gradle-aware Make

遇到的第二个问题就是在Debug Configuration中的Before launch,我只是添加了自定义的一个external tool,但是经常发现Android Studio会自动给我加上Gradle-aware Make,导致每次远程编译结束又开始本地编辑一遍。

出现这个问题的原因是Android Studio发现before launch里没有Gradle-aware Make会以为软件出了问题,进而进行了自动修复,这个问题的解决办法就是修改Android Studio的配置文件,让它不要自动修复:

  • 打开Android Studio的选项卡Help,找到Edit Custom VM Options选项,打开后出现一个叫studio的文件
  • 在文件的最后添加一行:-Dgradle.ide.gradle.run.configuration.fix.enabled=false

用了一段时间了,暂时就发现这两个问题,❤️

四、讨论

Mainframer很好的中和了我手中Mac笔记本的低配置和Windows台式机的不便携,将Mac的轻薄、便携和Windows的高配较好的结合起来。使我愉快的在使用小尺寸mac的同时,能得到最高的工作效率。但是这只是Mainframer的一个小领域应用,其实Mainframer可以用在更多实用的地方,比如使用集群作为远程机器,让大家可以连接进行远程编译等等。大家有什么想法,欢迎积极留言交流哈。

Author

calvinche

Posted on

2018-10-12

Licensed under

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×