前言

本文在引导你如何将AOSP (Android Open Source Project)完整导入企业内部或个人Gerrit伺服器。AOSP包含近800个专案,要如何有效快速无误的将这些专案导入Gerrit对很多IT人员来说若不知方法会是很头痛的问题。

本文是系列文的第二篇,本系列在介绍如何建立本地自有(local host) Gerrit Server并且将AOSP (Android Open Source Project)的原始码完整导入此Gerrit Server。

系列分为三个部份,本文是第二部份,教导如何建立AOSP Git Mirror并且同步导入自建的Gerrit Server,如果你还不知如何架设一个Gerrit Server,可以回到这系列的

第一部份:架设Gerrit Server,
第二部份:将AOSP完整导入Gerrit Server (本文)。
第三部份:如何从本地Gerrit Server建立一个AOSP Branch。

进行此AOSP导入工作的主要目的在于本人专业工作上需要修改AOSP原始码以符合客制化的需求,但AOSP本身庞大的原始码及代码库(Repository)不适合再导入公司内部原有已存在的Gerrit Server。于是乎,将AOSP导入一个新建的Gerrit Server成为一个合理的选择。另外更重要的原因是,自身内部专案的AOSP原始码必须能够提供以下功能:

  • 进行Code Review,确保稳定性及纪录留存以保留技术知识。
  • 同步Merge Android官方原始码,确保官方修正的问题可以同步合入自身专案。

在完成本文中所述的任务之后,你应该可以:

  • 建立基本Gerrit Server的能力
  • 抓取AOSP并导入Gerrit Server的能力
  • 同步AOSP并且合入官方修改的原始码的能力
  • 建立自身AOSP Branch并且修改及进行Code Review

本文在Ubuntu 18.04进行,基本上我在Ubuntu 16.04也执行过同样任务,所以差别不大,所以你的系统是在Ubuntu 16.04,应该完全可以适用。

所需软体

  • Gerrit (version 2.16.4)
  • Git
  • Open JDK 8
  • Apache2 (非必要)
  • MySQL (非必要)
  • Gerrit Delete Project Plug-in (非必要,但很好用)

概念

AOSP的相关资讯都在Android的官方网站(https://source.android.com/),如果你还不曾完整Build过Android的Codebase (不论是AOSP,Qualcomm还是MTK),那么你必须从:

https://source.android.com/setup/build/requirements

这里开始把Build Code的环境设定好。

AOSP是由一大堆Projects组成(本文选写时,大约七八百个Project),利用repo这个命令管理。如果使用上一篇方法来的一个一个建立Projects,不仅旷日费时,也容易出错。

但如果把repo命令,git mirror概念,gerrit命令的基本功能都弄清楚,则之后的导入同步及错误修正就不会很难。

什么是Git Mirror?

用很简单的直接的讲法就是「完全复制一份出来」。以往利用git clone取得的Project,是针对单一branch取出原始码。而git clone --mirror则是把所有的branches/tags都取出来。就是AOSP上所有的原始码你可以一次性下载。那也就是我们的目的。

所以基本的做法很简单,用以下步骤来完成转移AOSP的工作。

  1. 取得一份完整的AOSP Git Repository (Git mirror)
  2. 把AOSP Git mirror中所有的Projects在Gerrit创建一次。
  3. 把所有的AOSP Git mirror中所有的branches/tags都push进Gerrit Server
  4. 留下AOSP Git Mirror做同步化,官方新的Patch可同步至本地Gerrit Server

概念很清楚简单,但执行起来却有很多细节处理。包含权限设定,错误处理,架构规划,都可能会让你卡住很久。所以我在做第一次转移AOSP的动作时,也花了三天以上的时间。所以本文就是让你能一次性完成,不必走一次一样的冤枉路。

执行AOSP移转汇入本地Gerrit Server

  • 取得AOSP Git Repository (Mirror)

首先取得完整的AOSP Git Repository。取得的位址及方法在以下官网描述

https://source.android.com/setup/build/downloading

在官网中告诉你下载的方式为:repo init -u https://android.googlesource.com/platform/manifest

但这个方式是取得master branch,或是在其后加入-b 以取得其他Branch。
本文要完整取出所有的branch及tags,就必须加入–mirror参数。在本文范例中,我们先在另一台主机Gerrit Client建立一个目录aosp_mirror,存放AOSP mirror

mkdir aosp_mirror
cd aosp_mirror
repo init -u https://android.googlesource.com/platform/manifest --mirror
repo sync

Copy

所以重点在–mirror这个参数,让我们可以完整取得AOSP所有Repository。注意,取得AOSP Repostory必须是在Gerrit Server有Administrator权限的人,在PART1中,此人为larson。所以不一定要在Gerrit Server同一台执行,可以在远端Client (以下称之为Gerrit Client)取回AOSP Repository后,透过git push将原码推至Gerrit Server。最后用repo sync取得原始码,这会花一段时间,我大概花了三个小时下载,因为大概有100 GBytes以上的资料量。

  • 在Gerrit Server建立所有Projects的前期工作

建立所有的Projects之前我们先建立一个Parent Repository–AOSP。(若你还没有Gerrit server及Gerrit Web UI,请先至PART1建立)。
在Gerrit Web UI中,点选Browse->Repositories,点击「CREATE NEW」

输入名称为AOSP,在「Only server as parent for other repositories」选择True。按下「CREATE」,完成建立。

AOSP只是个Parent Repository,主要是要把所有AOSP下的七百多个Projects在Gerrit上的权限在统一管理。之后加入的Projects将Parent设为AOSP,我们只要在AOSP下调整权限,就可以等同调整所有Projects的权限,同时也不影响其他已存在Gerrit Server的Projects的权限。

然后我们加入两个Group (成员群组),android-admin及android-coder,刚刚加入的Parent Project “AOSP”是把相关的Projects做一个群组,而成员群组也是同样的概念,把人加入某一个成员群组后,该成员的权限就能统一管控。

在Gerrit Web UI中,点选Browse->Groups,点击「CREATE NEW」,输入android-admin,按CREATE,建立群组。

再重复以上动作一次,加入android-coder。因为创立者是预设member,所以点选android-admin/android-coder后,再点选左侧的Memebers,就可以看到有哪些人在此群组。

android-admin顾名思义,就是管理AOSP Projects的人,包含建立/删除/Merge/Review等管理工作,而android-coder就是把修改的Code推上Gerrit等待Review,请android-admin进行Review后Commit或Abandon。

所以接下来就是设定AOSP的权限:
\1. 在Gerrit Web UI中,点选BROWSE->Repositories,选择或搜寻AOSP。点选进入。
\2. 在左侧栏,点选Access。
\3. 在中央主画面中,点击Edit。开始编辑权限
\4. 此时会有第一个Preference出现,把ref/for/改为ref/
\5. 下拉Add permission:,选择Push,点选右边的Add。此时有个Push权限加入。
\6. 在Push下方的"add group"文字栏中,输入android-admin,按Enter (或输入至一半时,若跳出选单,选android-admin即可)。
\7. 重覆步骤5和6,加入Forge Author Identity和Forge Committer Identity。
\8. 结果如下:

每个权限的意义可以至官网查询,这里不再重述。以上的权限其实还不足,也不够细致。但主要是在汇入AOSP所有Projects时,不会有权限问题而推入失败。

  • 在Gerrit Server汇入所有Projects

接下来是把七百多个Projects都建在Gerrit Server上。回到Gerrit Client主机上,进入刚刚建立的aosp_mirror目录中。你可以先输入:

repo forall -c 'echo $REPO_PROJECT'

Copy

来看所有的Projects,这个命令repo forall -c就是「在所有Projects执行命令」,而$REPO_PROJECT这变环境变数会随着当前的有不同的值,其值就是Project Name。
所以以上的命令就是把所有的Project Name列出来,而没有对Git/Gerrit做任何操作。

第一步,在Gerrit上新建所有Projects
理解了上述repo forall -c及$REPO_PROJECT变数后,以下的建立Projects的命令就很容易了解。

repo forall -c 'echo $REPO_PROJECT; ssh -p 29418 larson@192.168.100.56 gerrit create-project --owner android-admin $REPO_PROJECT;'

Copy

repo forall -c 'echo $REPO_PROJECT; ssh -p 29418 larson@192.168.100.56 gerrit set-project-parent --parent AOSP $REPO_PROJECT;'

Copy

第一个指令是建立所有Projects;第二个指令是把所有的Projects的Parent Project设为AOSP,所有的AOSP Projects的父专案就是AOSP,将来只要改变AOSP的权限就能设定所有Project存取权限。

repo forall -c是对每个Project做动作,echo是列出目前要重新建立在Gerrit Server的Project Name,如果发生了任何的Error Message,你也会比较容易知道是哪个Project出问题。真正执行建立Project的命令是:ssh -p 29418 larson@192.168.100.56 gerrit create-project --owner android-admin REPOPROJECT;ssh−p29418是用SSH的29418Port通讯(GerritServer预设Port),larson是Adminstratorusername,192.168.100.56是ServerIP。gerritcreate−project是Gerrit命令,用来建立新的Project,−−owner是Project管理者,所以是android−admin这个群组。最后repoforall替你把REPO_PROJECT; ssh -p 29418是用SSH的29418 Port通讯(Gerrit Server预设Port),larson是Adminstrator username, 192.168.100.56是Server IP。gerrit create-project是Gerrit命令,用来建立新的Project,--owner是Project管理者,所以是android-admin这个群组。最后repo forall替你把REPOP​ROJECT;ssh−p29418是用SSH的29418Port通讯(GerritServer预设Port),larson是Adminstratorusername,192.168.100.56是ServerIP。gerritcreate−project是Gerrit命令,用来建立新的Project,−−owner是Project管理者,所以是android−admin这个群组。最后repoforall替你把REPO_PROJECT换成真正的Project Name。
以上的指令应该不会碰上什么困难,就是把700多个Projects整个Looping一次,建立好,设定Parent Project。
此时你可以回到Gerrit Web UI,看一下所有Projects是否成功建立。

可以看出已经有一大堆Projects列在Gerrit Web UI上了。

第二步,把所有的原始码汇入刚刚建立的Projects:
在执行全部专案原始码汇入之前,回到Gerrit Server主机,修改aosp_review_site/etc/gerrit.config并且重启gerrit service。修改如下:

[gerrit]basePath = /home/gerrit/aosp_gitserverId = 03a2e828-86e2-47ac-abf2-facca5260695canonicalWebUrl = http://192.168.100.56:8080/
[database]type = mysqlhostname = localhostdatabase = reviewdbusername = gerrit
[noteDb "changes"]disableReviewDb = trueprimaryStorage = note dbread = truesequence = truewrite = true
[container]javaOptions = "-Dflogger.backend_factory=com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance"javaOptions = "-Dflogger.logging_context=com.google.gerrit.server.logging.LoggingContext#getInstance"user = gerritjavaHome = /usr/lib/jvm/java-8-openjdk-amd64/jre
[index]type = LUCENE
[auth]type = OPENID
[receive]enableSignedPush = falsemaxBatchCommits = 1000000timeout = 120min
[sendemail]smtpServer = localhost
[sshd]listenAddress = *:29418
[httpd]listenUrl = http://*:8080/
[cache]directory = cache
[plugins]allowRemoteAdmin = true

加入第27,28行(Highlighten),maxBatchCommits=1000000是为了解决错误讯息为(more than 10000 commits, and skip-validation not set),而timeout=120m是修正在汇入时,由于Project过于庞大,造成过时问题。做一次汇入的时间很长,所以建议先改好gerrit.config,重启gerrit server (gerrit.sh restart)之后再进入汇入工作。如果不改gerrit.config,在执行汇入时,就会发生类似错误。

repo forall -c 'echo $REPO_PROJECT; git push ssh://larson@192.168.100.56:29418/$REPO_PROJECT +refs/heads/*'
platform/art
Counting objects: 302384, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (107448/107448), done.
Writing objects: 100% (302384/302384), 199.43 MiB | 16.72 MiB/s, done.
Total 302384 (delta 193646), reused 299963 (delta 191268)
remote: Resolving deltas: 100% (193646/193646)
remote: Counting objects: 302384, done
remote: Processing changes: refs: 140, done
To ssh://192.168.100.56:29418/platform/art* [new branch]            idea133 -> idea133* [new branch]            idea133-weekly-release -> idea133-weekly-release* [new branch]            kitkat-cts-dev -> kitkat-cts-dev* [new branch]            kitkat-cts-release -> kitkat-cts-release* [new branch]            kitkat-dev -> kitkat-dev* [new branch]            kitkat-mr1-release -> kitkat-mr1-release* [new branch]            kitkat-mr1.1-release -> kitkat-mr1.1-release* [new branch]            kitkat-mr2-release -> kitkat-mr2-release* [new branch]            kitkat-mr2.1-release -> kitkat-mr2.1-release* [new branch]            kitkat-mr2.2-release -> kitkat-mr2.2-release* [new branch]            kitkat-release -> kitkat-release* [new branch]            kitkat-wear -> kitkat-wear! [remote rejected]       l-preview -> l-preview (more than 10000 commits, and skip-validation not set)! [remote rejected]       lollipop-cts-release -> lollipop-cts-release (more than 10000 commits, and skip-validation not set)! [remote rejected]       lollipop-dev -> lollipop-dev (more than 10000 commits, and skip-validation not set)! [remote rejected]       lollipop-mr1-cts-release -> lollipop-mr1-cts-release (more than 10000 commits, and skip-validation not set)

可以看出错误讯息为(more than 10000 commits, and skip-validation not set),而maxBatchCommits=1000000这个设定可以解决该问题。
接下来就真正进入Project汇入Gerrit Server的作业了。
对每一个AOSP Project执行git push的命令,将原始码汇入:

repo forall -c 'echo $REPO_PROJECT; git push ssh://larson@192.168.100.56:29418/$REPO_PROJECT +refs/heads/*'
repo forall -c 'echo $REPO_PROJECT; git push ssh://larson@192.168.100.56:29418/$REPO_PROJECT +refs/heads/* +refs/tags/*'

此时视你内部网路及主机的效能而定,但仍需要约30分钟至1小时以上的时间完成整个汇入的动作(花了两三小时才汇入也是正常)。

你可以看出我用了两个很相似的命令去汇入原始码,第一个是只推heads (branchs) 另一个加上tags。这看起来有点瞎,但在性能不太强大的Gerrit Server主机是必须的。否则会由于效能不足(RAM?)而发生internal server error!

下完命令后,大部份的Project都可以顺利被汇入gerrit server,最可能出错的Project是platform/manifest,如果你看到以下错误讯息(internal server error):

To ssh://larson@192.168.100.56:29418/platform/manifest! [remote rejected] adt_23.0.3 -> adt_23.0.3 (internal server error)! [remote rejected] afw-test-harness-1.5 -> afw-test-harness-1.5 (internal server error)! [remote rejected] afw-test-harness-2.1 -> afw-test-harness-2.1 (internal server error)! [remote rejected] afw-test-harness-marshmallow-dev -> afw-test-harness-marshmallow-dev (internal server error)! [remote rejected] afw-test-harness-nougat-dev -> afw-test-harness-nougat-dev (internal server error)! [remote rejected] android-1.6_r1 -> android-1.6_r1 (internal server error)! [remote rejected] android-1.6_r1.1 -> android-1.6_r1.1 (internal server error)! [remote rejected] android-1.6_r1.2 -> android-1.6_r1.2 (internal server error)! [remote rejected] android-1.6_r1.3 -> android-1.6_r1.3 (internal server error)! [remote rejected] android-1.6_r1.4 -> android-1.6_r1.4 (internal server error)! [remote rejected] android-1.6_r1.5 -> android-1.6_r1.5 (internal server error)

以经验而言,Gerrit Server主机端发生问题,很多的状况是RAM不足造成的。但也不要傻傻的就跑去扩充RAM(我的Gerrit Server有8G RAM,但在push manifest时,还是出现internal server error!)。仔细检视platform/manifest这个Projects,很明显它的refs非常多,所以如果这是个造成问题的变数,那我们可以各别ref推入的方式来解决这个问题。
首先回到Gerrit Client,进入aosp_mirror/platform/manifest.git

for n in $(git for-each-ref --format='%(refname)' refs/heads); do git push ssh://larson@192.168.100.56:29418/platform/manifest $n; done
for n in $(git for-each-ref --format='%(refname)' refs/tags); do git push ssh://larson@192.168.100.56:29418/platform/manifest $n; done

一样分两个命令,一个把所有Branch推入,另一个把所有Tags推入。这两个命令就是先列出所有Branch/Tag后,逐个推入,这样就不会有因RAM不足而造成的错误。缺点就是比较花时间。(但一定比去扩充RAM省时省钱,至于RAM有扩到多大才够?我也没试过!当然也有可能是Gerrit的编程时,没考虑如此大量的refs时的状况,所以在gerrit server发生问题时,我们从error log也看不出什么问题,因为也没看见任何Exception!!这只有真的去钻Source Code才能得到答案了。)

  • 验证,取回原始码并且Build出Image在Emulator执行

目前我们取得的是AOSP Master branch,也就是最新的原始码,通常都可以Build过。但如果你就这么刚好在两个Commit中间取出原始码,Build Fail也不是不可能的事。

要验证我们的Gerrit是不是没有问题,第一步就是把AOSP整个取出,之后Build过一次:

repo init -u ssh://larson@192.168.100.56:29418/platform/manifest
repo sync
source ./build/envsetup.sh
lunch aosp_x86_64-eng #官方Build出X86 Emulator的方法
make -j16 #開始執行Build

Build好之后,执行Android Emulator,在Emulator中,Settings->System->About emulated device,检视Build Number,是你Build的日期及user name,也就确认此Emulator是由你自己Build出来的,也确认我们把AOSP汇入再取出后是完整的AOSP Codebase。

  • 同步官方AOSP

完成AOSP Mirror转移汇入本地Gerrit Server的工作后,不要就把Gerrit Client中的aosp_mirror干掉!因为将来和官方AOSP的同步还要靠它!例如AOSP又出了一个Android Q/R…,或是用到不同平台装置,例如IoT/Car,的Branch。这时如果你必须同步这些Code,就在aosp_mirror下,执行repo sync,把原始码同步后,再汇入你的gerrit server。先回到Gerrit Client的aosp_mirror目录,同步官方的AOSP方式:

repo sync -j8 #同步官方AOSP
repo forall -c 'echo $REPO_PROJECT; ssh -p 29418 larson@192.168.100.56 gerrit create-project --owner android-admin $REPO_PROJECT' #若有新的Project,就建立它,已經存在的Projects會回應Fatal error,這是正常的,無視
repo forall -c 'echo $REPO_PROJECT; ssh -p 29418 larson@192.168.100.56 gerrit set-project-parent --parent AOSP $REPO_PROJECT' #若有新的Project,將Parent設為AOSP
repo forall -c 'echo $REPO_PROJECT; git push ssh://larson@192.168.100.56:29418/$REPO_PROJECT +refs/heads/* +refs/tags/*' #匯入所有新增的Code,完成同步

建立自己的分支Branch

一般而言,在AOSP上工作的研发人员不会在Master branch上修改Code。因为此时的Code未必稳定,而且可能Build不出来,也可能Build出来了,但常发生Crash。Master branch是最经常改Code的地方,如果在这个分枝工作,势必常常面临Merge Code的工作。

所以一般来说我们会在相对稳定的Branch/Tag上,建立自有的分枝后,在自有分枝上工作。这也是我们需要一个自有Gerrit Server并把AOSP完整移转过来的最初原因。
如何建立分枝,可以看PART3,建立AOSP分枝并建立在Gerrit Server。

虽然本文是要「完整的」移转汇入AOSP,但事实上我们现在做完的移转是不完整,而且很多Projects其实根本还没汇入。为什么?主要是因为AOSP Master branch并不是包含所有Projects,而是目前最新的Android版本中的Projects。例如,AOSP Master branch在本文发表时,是Android Q,而上个版本Android P中有个Project名称为framework/data-binding,但在Android Q因架构改变,不再纳入此Project。所以我们的Gerrit server没有这个Project。那么如果我要在Pixel 2上的Android P上修改Code,并且把这个修改纳入Gerrit review,成为自己的ROM,及内部知识保留(这也是本文一开始说的目的!)但我的Gerrit server没有framework/data-binding这个Projects,怎么办?那这就是PART3要讨论的部份了!

搭建本地AOSP Gerrit Server完全指南 (二)相关推荐

  1. 搭建本地GitLab仓库排坑指南

    关于GitLab GitLab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的Web服务.安装方法是参考GitLab在GitHub上的Wiki页面. 2022年2 ...

  2. 群晖Nas通过jellyfin搭建本地影音库详细全过程(二):jellyfin影音库信息手动刮削和相关设置(100%扫库成功)

    前言,上期讲了如何安装jellyfin,根据我个人使用感受,我很开心的将收藏的电影复制到媒体库中,在jellyfin中新建了媒体库,然后发现自动刮削就是shit,还有一大半电影直接不显示,很多人可能会 ...

  3. svn 使用TortoiseSVN server搭建本地SVN服务器

    使用TortoiseSVN server搭建本地SVN服务器 转载于:https://www.cnblogs.com/fireblackman/p/10799625.html

  4. 【QT 数据库专辑】【04】WIN7下搭建本地SQL SERVER数据库 - 手把手-登录远程数据库帐号设定问题

    前言: 多次数据库试验经验报名,远程数据库的访问问题,大多数是因为有帐号和访问权限的原因. 本文,通过从安装SQL SERVER开始暂时,我们在远程访问SQL SERVER数据库的时候可能遇到的问题. ...

  5. 基于【CentOS-7+ Ambari 2.7.0 + HDP 3.0】搭建HAWQ数据仓库01 —— 准备环境,搭建本地仓库,安装ambari...

    一.集群软硬件环境准备: 操作系统:  centos 7 x86_64.1804 Ambari版本:2.7.0 HDP版本:3.0.0 HAWQ版本:2.3.0 5台PC作为工作站: ep-bd01 ...

  6. iOS开发网络篇—搭建本地服务器

    iOS开发网络篇-搭建本地服务器 一.简单说明 说明:提前下载好相关软件,且安装目录最好安装在全英文路径下.如果路径有中文名,那么可能会出现一些莫名其妙的问题. 提示:提前准备好的软件 apache- ...

  7. 解决内网搭建本地yum仓库。

    2019独角兽企业重金招聘Python工程师标准>>> 一.使用iso镜像搭建本地yum仓库: 1.挂载镜像到/mnt目录下: [root@Dasoncheng ~]# mount ...

  8. webpack-dev-server 搭建本地服务以及浏览器实时刷新

    一.概述 开发项目中为了保证上线,开发项目是都需要使用localhost进行开发,以前的做法就是本地搭建Apache或者Tomcat服务器.有的前端开发人员 对服务器的搭建和配置并不熟悉,这个时候需要 ...

  9. Dnsmasq安装与配置-搭建本地DNS服务器 更干净更快无广告DNS解析

    Dnsmasq安装与配置-搭建本地DNS服务器 更干净更快无广告DNS解析 文章目录 Dnsmasq安装 Dnsmasq配置 Dnsmasq启动 Dnsmasq使用 Dnsmasq小结 默认的情况下, ...

最新文章

  1. 几句话描述简单算法——排序与搜索
  2. mysql重置auto_increment字段
  3. Java集合框架:Set(HashSet,LinkedHashSet,TreeSet)
  4. leetcode 621. Task Scheduler | 621. 任务调度器(Java)
  5. hybris导出系统已有数据的两种方式
  6. SpringBoot与日志配置
  7. 2021年中国数字化采购研究报告
  8. JS格式化时间之后少了8个小时
  9. 【洛谷 P1070】道路游戏 (DP)
  10. 黑塞矩阵-二阶偏导矩阵
  11. android 获取经纬度的三种方法,Android获取经纬度
  12. 30. 人类将如何变革--走出金字塔模型(下)
  13. c#向pdf插入图片,使用iTextSharp【实测成功】
  14. 各数据库远程连接及ipv6环境配置
  15. 自动排课系统V2.0基本完善了
  16. 大锅菜机器人_天津农学院现“炒菜机器人” 做番茄炒蛋堪比大师傅
  17. 小说中场景的功能_一般文章里的场景描写有什么作用啊详细一些的 最好在
  18. 家用电器的CCC认证流程
  19. 使用python获取股票“净利润同比增长率”等“上市公司成长能力”数据
  20. 【STM32F767】使用RTThread和TouchGFX实现DIY数字仪表(三)——获取温湿度传感器数据

热门文章

  1. 孟塞尔测试软件,色差仪的孟塞尔颜色系统表示法
  2. poj2228Naptime——环形DP
  3. 时间序列预测 | Python实现Transformer时间序列数据预测
  4. sqlserver嵌套查询失败问题
  5. 基于C51单片机和TB6600步进电机驱动器驱动的三路超声波避障移动机器人平台设计与Proteus仿真
  6. 爬虫逆向学习进阶路线
  7. matplotlib.plot显示希腊字母及标题中的平方函数
  8. Xposed插件的使用(一)进行简单的Hook
  9. 莫比乌斯反演入门讲解
  10. 2018年第二届河北省大学生程序设计竞赛