一加9R编译烧录自己的LineageOS系统过程

1,990 阅读8分钟

背景

最近换了个新手机,工作三年的一加9r也该退休了,但是其实基本没什么问题主要是想换华为了,跟我其他设备交互更方便点,然后这台一加其实退休前就想了,让它继续发光发热,刷到LineageOS,因为之前只有工作项目才能搞点自定义的系统东西,下班后也不好带回家,好好好,这次我自己搞一个,刚好就是大名鼎鼎的LineageOS,刷完后还可以自己来编译刷机,方便练手。

不得不说这个比小米的方便多了,起码是从解锁阶段,我拿我妹的看了一下解锁时间然后就放弃了,话不多说,这次主要进行的有以下几步:

  1. 解锁设备
  2. 刷入LineageOS 21
  3. Ubuntu20.04环境拉取并编译自己的LineageOS 21,刷入手机

建议在操作之前一定看下我最后的注意事项,亲身经历的,文字太多看起来可能有疏忽,避免过程中出现问题

首先,要确保

  1. 当前是Andorid13的时候再进行后面的操作,我当时不以为然,然后就变砖了,找人刷到氧13的
  2. 确保有adbfastboot环境

一、解锁设备

  1. 第一步是准备工作。进入手机的设置-关于手机-版本号,然后连击10下版本号,直到激活隐藏的开发者选项,手机下方位置会有toast提示告知;然后回到手机设置,点开系统-开发者选项,找到OEM解锁以及USB调试,这俩都打开,前者是允许你通过fastboot进行刷机操作的,后者是允许你开机状态用电脑控制手机的。
  2. adb reboot bootloader,如果手机上弹出usb调试模式询问的弹窗,请授权同意一下,如果点慢了,电脑上会报错,就重新输入一次指令。

指令成功运行后,手机会重启到一个黑底并且一堆英文的界面,这个就是Bootloader界面了:

还没解锁的话,最后一行的DEVICE STATE应该是LOCKED的状态,解锁完了就是UNLOCEKED了。看到这个手机界面之后,再在电脑上输入指令:

fastboot oem unlock

然后会进入到一大段的提示界面,你可以通过音量键上下选择,下图第一行是不解锁,第二行是继续解锁的意思,按电源键确认;

我们是要解锁的,所以用音量键向下选择unlock the bootloader,就可以完成解锁了。解锁后,手机会自动重启

二、刷lineageOS

先在LineageOS官网中找到自己设备的型号,并选择对应的版本下载,我这里下载的是最新的0628的

download.lineageos.org/devices/lem…

1. 下载如图中这几个包

image.png

2. 刷入下载img

先进入fastboot模式通过以下方式: 设备关机,长按音量+和音量-和Power键, 通过fastboot devices查看是否有连接的设备,并依次输入

fastboot flash dtbo dtbo.img
fastboot flash vbmeta vbmeta.img
fastboot flash recovery recovery.img

然后使用音量键选择Recovery选项,进入到Recovery模式,此时再重启应该LineageOSRecovery界面

3. 确保所有固件分区一致
  1. 从这个链接下载:mirrorbits.lineageos.org/tools/copy-…(适用于一加9r,其他设备的需要在官网寻找)
  2. 下载后在设备上点击Apply Update,然后点击Apply from ADB来开始等待安装
  3. 在PC上输入以下命令开始安装:adb -d sideload copy-partitions-20220613-signed.zip
  4. 过点击Advanced来重启进入recovery,然后点击Reboot to recovery
  5. 进入recovery后,点击Factory Reset,然后Format data/factory reset来格式化数据
  6. 点击屏幕上返回键到主按钮界面
4. 不要重启此时准备好前面下载的lineageos
  1. 在设备上,点击Apply Update,再点击Apply from ADB
  2. 在PC上,输入adb -d sideload filename.zip,更换为具体的lineageOS包名

如果卡在47%,但是手机已经跳转到Recovery也算成功

5.此时可以去安装附加的其他东西,比如Google Apps等,都是通过进入指定界面,输入adb -d sideload filename.zip的方式,如果不需要的还直接选择重启系统即可。

三、自己编译系统并烧入手机

到重头戏了,自己烧别人的用有什么意思,不如自己改改再玩会更有成就感,以下是在Ubuntu20.04上拉取和编译LineageOS 21的代码,Android14

1. 先在系统中把adb,fastboot环境安装好
2. 安装依赖,根据不同系统选择
sudo apt-get install bc bison build-essential ccache curl flex g++-multilib gcc-multilib git gnupg gperf imagemagick lib32ncurses5-dev lib32readline-dev lib32z1-dev libelf-dev liblz4-tool libncurses5 libncurses5-dev libsdl1.2-dev libssl-dev libxml2 libxml2-utils lzop pngcrush rsyncschedtool squashfs-tools xsltproc zip zlib1g-dev
3. 创建文件夹做编译环境
mkdir -p ~/bin
mkdir -p ~/android/lineage
4. 安装repo
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo
5. 配置环境

检查~/.profile看是不是下面这样:

# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin:$PATH"
fi

然后执行source ~/.profile来更新环境

6. 配置git
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
7. 初始化lineageOS源码仓库
cd ~/android/lineage
repo init -u https://github.com/LineageOS/android.git -b lineage-21.0

在国内repo init最好先修改下REPO_URL,修改方法:

vim ~/bin/repo

修改后:

REPO_URL ='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/'

repo init后,需要对.repoxml进行修改,如果直接repo sync,国内会报很多错误,主要还是因为网络问题,解决方法如下: 对.repo/manifests/default.xml进行修改,修改具体点如下:

diff --git a/default.xml b/default.xml
index 672a8c3..da6b283 100644
--- a/default.xml
+++ b/default.xml
@@ -1,20 +1,21 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <manifest>
 
-  <remote  name="github"
-           fetch=".."
+   <remote  name="github"
+           fetch="https://github.com/" />
+  <remote  name="lineage"
+           fetch="https://mirrors.tuna.tsinghua.edu.cn/git/lineageOS/"
            review="review.lineageos.org" />
 
   <remote  name="private"
            fetch="ssh://git@github.com" />
 
   <remote  name="aosp"
-           fetch="https://android.googlesource.com"
-           review="android-review.googlesource.com"
+           fetch="https://mirrors.tuna.tsinghua.edu.cn/git/AOSP"
            revision="refs/tags/android-12.1.0_r22" />
 
   <default revision="refs/heads/lineage-19.1"
-           remote="github"
+           remote="lineage"
            sync-c="true"
            sync-j="4" />
8. 下载源码
repo sync
9. 配置设备独立代码,device,kernel,vendor
# 进入源码目录~/android/lineage
source build/envsetup.sh
# lemonades是一加9r的代号,如果其他机型可以在LineageOS官网中具体查看
breakfast lemonades

这时可能会失败,我用一加9r会提示product未知等信息,需要我们准备下该设备的相关配置

# device
https://github.com/LineageOS/android_device_oneplus_lemonades
https://github.com/LineageOS/android_device_oneplus_sm8250-common.git

# kernel
https://github.com/LineageOS/android_kernel_oneplus_sm8250.git

进入~/android/lineage/device/目录下,git clone上面device注释下的两个内容,clone完成后,放到对应的目录中,举个例子,如第一个github.com/LineageOS/a…,即将clone完成的文件夹放到/device/oneplus/,并将文件夹改名为lemonades保存,kernel也是同理,只是保存到kernel的指定目录,文件夹没有则手动建立

最后成这样:

其他不同的设备,可以在github中搜索device_xx设备类型,如device_xiaomi,同样的方法,根据网上一些教程,看有些设备还可以直接下载vendor,但是一加9r的我没有找到,所以还需要做下面的操作:

10. 解压元数据

英文是Extract proprietary blobs,获取元数据有两种方式

  1. 设备已经是lineageOS了,连接上PC,并root,并关闭selinux权限,setenforce 0, 这种方式设备确保已经连接上PC并且USB调试打开,执行如下:
cd ~/android/lineage/device/oneplus/lemonades/
./extract-files.sh

它会自动去下载lemonades的依赖, 如果你前面已经完成设备上烧录lineageOS的话直接按该方式即可。

  1. 先下载最新的lineageOS包,再从中提取lineageOS包中提取 第二种方式需要先下载LineageOS
# 地址也是从官网上获取,复制其链接
wget https://mirrorbits.lineageos.org/full/lemonades/20240628/lineage-21.0-20240628-nightly-lemonades-signed.zip

再安装一些用来解压的工具并创建解压的文件夹

mkdir ~/android/system_dump/
cd ~/android/system_dump/

sudo apt-get install python3-protobuf
git clone https://github.com/LineageOS/android_prebuilts_extract-tools android/prebuilts/extract-tools
git clone https://github.com/LineageOS/android_tools_extract-utils android/tools/extract-utils
git clone https://github.com/LineageOS/android_system_update_engine android/system/update_engine

使用unzip解压刚才下载的LineageOS包,这里的path/to/改为自己的实际路径

unzip path/to/lineage-*.zip

然后通过刚才下载的解压工具进行解压,当前是在刚才clone的目录~/android/system_dump/中,path/to/改为自己的实际路径

./android/prebuilts/extract-tools/linux-x86/bin/ota_extractor --payload path/to/payload.bin

等一会儿成功后,需要挂载以下内容

mkdir system/
sudo mount -o ro system.img system/
sudo mount -o ro vendor.img system/vendor/
sudo mount -o ro odm.img system/odm/
sudo mount -o ro product.img system/product/
sudo mount -o ro system_ext.img system/system_ext/

切换到~/android/lineage/device/oneplus/lemonades/目录中开始获取元数据

cd ~/android/lineage/device/oneplus/lemonades/
./extract-files.sh ~/android/system_dump/

这样就告诉./extract-files.sh解压的元数据来源是从system_dump而不是连接的设备中

11. 下载设备内核仓库地址

提取成功之后就再回到根目录输入

source build/envsetup.sh
breakfast lemonades

此时可能会报错,如linageos Device lemonades not found,缺什么文件等,最主要的是有个Failed to fetch data from GitHub,主要是网络无法访问的原因,可以参考blog.csdn.net/learnframew…,有个路径无法网络访问,我这边是直接把vendor/lineage/build/tools/roomservice.py文件内容备份后,换为以下内容:

#!/usr/bin/env python
# Copyright (C) 2012-2013, The CyanogenMod Project
#           (C) 2017-2018,2020-2021, The LineageOS Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import print_function

import base64
import json
import netrc
import os
import re
import sys
try:
    # For python3
    import urllib.error
    import urllib.parse
    import urllib.request
except ImportError:
    # For python2
    import imp
    import urllib2
    import urlparse
    urllib = imp.new_module('urllib')
    urllib.error = urllib2
    urllib.parse = urlparse
    urllib.request = urllib2

from xml.etree import ElementTree

product = sys.argv[1]

if len(sys.argv) > 2:
    depsonly = sys.argv[2]
else:
    depsonly = None

try:
    device = product[product.index("_") + 1:]
except:
    device = product

if not depsonly:
    print("Device %s not found. Attempting to retrieve device repository from LineageOS Github (http://github.com/LineageOS)." % device)

repositories = []

try:
    authtuple = netrc.netrc().authenticators("api.github.com")

    if authtuple:
        auth_string = ('%s:%s' % (authtuple[0], authtuple[2])).encode()
        githubauth = base64.encodestring(auth_string).decode().replace('\n', '')
    else:
        githubauth = None
except:
    githubauth = None

def add_auth(githubreq):
    if githubauth:
        githubreq.add_header("Authorization","Basic %s" % githubauth)

if not depsonly:
    githubreq = urllib.request.Request("https://api.github.com/search/repositories?q=%s+user:LineageOS+in:name+fork:true" % device)
    add_auth(githubreq)
    try:
        result = json.loads(urllib.request.urlopen(githubreq).read().decode())
    except urllib.error.URLError:
        print("Failed to search GitHub")
        sys.exit(1)
    except ValueError:
        print("Failed to parse return data from GitHub")
        sys.exit(1)
    for res in result.get('items', []):
        repositories.append(res)

local_manifests = r'.repo/local_manifests'
if not os.path.exists(local_manifests): os.makedirs(local_manifests)

def exists_in_tree(lm, path):
    for child in lm.getchildren():
        if child.attrib['path'] == path:
            return True
    return False

# in-place prettyprint formatter
def indent(elem, level=0):
    i = "\n" + level*"  "
    if len(elem):
        if not elem.text or not elem.text.strip():
            elem.text = i + "  "
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
        for elem in elem:
            indent(elem, level+1)
        if not elem.tail or not elem.tail.strip():
            elem.tail = i
    else:
        if level and (not elem.tail or not elem.tail.strip()):
            elem.tail = i

def get_manifest_path():
    '''Find the current manifest path
    In old versions of repo this is at .repo/manifest.xml
    In new versions, .repo/manifest.xml includes an include
    to some arbitrary file in .repo/manifests'''

    m = ElementTree.parse(".repo/manifest.xml")
    try:
        m.findall('default')[0]
        return '.repo/manifest.xml'
    except IndexError:
        return ".repo/manifests/{}".format(m.find("include").get("name"))

def get_default_revision():
    m = ElementTree.parse(get_manifest_path())
    d = m.findall('default')[0]
    r = d.get('revision')
    return r.replace('refs/heads/', '').replace('refs/tags/', '')

def get_from_manifest(devicename):
    try:
        lm = ElementTree.parse(".repo/local_manifests/roomservice.xml")
        lm = lm.getroot()
    except:
        lm = ElementTree.Element("manifest")

    for localpath in lm.findall("project"):
        if re.search("android_device_.*_%s$" % device, localpath.get("name")):
            return localpath.get("path")

    return None

def is_in_manifest(projectpath):
    try:
        lm = ElementTree.parse(".repo/local_manifests/roomservice.xml")
        lm = lm.getroot()
    except:
        lm = ElementTree.Element("manifest")

    for localpath in lm.findall("project"):
        if localpath.get("path") == projectpath:
            return True

    # Search in main manifest, too
    try:
        lm = ElementTree.parse(get_manifest_path())
        lm = lm.getroot()
    except:
        lm = ElementTree.Element("manifest")

    for localpath in lm.findall("project"):
        if localpath.get("path") == projectpath:
            return True

    # ... and don't forget the lineage snippet
    try:
        lm = ElementTree.parse(".repo/manifests/snippets/lineage.xml")
        lm = lm.getroot()
    except:
        lm = ElementTree.Element("manifest")

    for localpath in lm.findall("project"):
        if localpath.get("path") == projectpath:
            return True

    return False

def add_to_manifest(repositories):
    try:
        lm = ElementTree.parse(".repo/local_manifests/roomservice.xml")
        lm = lm.getroot()
    except:
        lm = ElementTree.Element("manifest")

    for repository in repositories:
        repo_name = repository['repository']
        repo_target = repository['target_path']
        repo_revision = repository['branch']
        print('Checking if %s is fetched from %s' % (repo_target, repo_name))
        if is_in_manifest(repo_target):
            print('LineageOS/%s already fetched to %s' % (repo_name, repo_target))
            continue

        print('Adding dependency: LineageOS/%s -> %s' % (repo_name, repo_target))
        project = ElementTree.Element("project", attrib = {
            "path": repo_target,
            "remote": "github",
            "name": "LineageOS/%s" % repo_name,
            "revision": repo_revision })
        lm.append(project)

    indent(lm, 0)
    raw_xml = ElementTree.tostring(lm).decode()
    raw_xml = '<?xml version="1.0" encoding="UTF-8"?>\n' + raw_xml

    f = open('.repo/local_manifests/roomservice.xml', 'w')
    f.write(raw_xml)
    f.close()

def fetch_dependencies(repo_path):
    print('Looking for dependencies in %s' % repo_path)
    dependencies_path = repo_path + '/lineage.dependencies'
    syncable_repos = []
    verify_repos = []

    if os.path.exists(dependencies_path):
        dependencies_file = open(dependencies_path, 'r')
        dependencies = json.loads(dependencies_file.read())
        fetch_list = []

        for dependency in dependencies:
            if not is_in_manifest(dependency['target_path']):
                fetch_list.append(dependency)
                syncable_repos.append(dependency['target_path'])
                if 'branch' not in dependency:
                    dependency['branch'] = get_default_or_fallback_revision(dependency['repository'])
            verify_repos.append(dependency['target_path'])

            if not os.path.isdir(dependency['target_path']):
                syncable_repos.append(dependency['target_path'])

        dependencies_file.close()

        if len(fetch_list) > 0:
            print('Adding dependencies to manifest')
            add_to_manifest(fetch_list)
    else:
        print('%s has no additional dependencies.' % repo_path)

    if len(syncable_repos) > 0:
        print('Syncing dependencies')
        os.system('repo sync --force-sync %s' % ' '.join(syncable_repos))

    for deprepo in verify_repos:
        fetch_dependencies(deprepo)

def has_branch(branches, revision):
    return revision in [branch['name'] for branch in branches]

def get_default_revision_no_minor():
    return get_default_revision().rsplit('.', 1)[0]

def get_default_or_fallback_revision(repo_name):
    default_revision = get_default_revision()
    print("Default revision: %s" % default_revision)
    print("Checking branch info")

    githubreq = urllib.request.Request("https://api.github.com/repos/LineageOS/" + repo_name + "/branches")
    add_auth(githubreq)
    result = json.loads(urllib.request.urlopen(githubreq).read().decode())
    if has_branch(result, default_revision):
        return default_revision

    fallbacks = [ get_default_revision_no_minor() ]
    if os.getenv('ROOMSERVICE_BRANCHES'):
        fallbacks += list(filter(bool, os.getenv('ROOMSERVICE_BRANCHES').split(' ')))

    for fallback in fallbacks:
        if has_branch(result, fallback):
            print("Using fallback branch: %s" % fallback)
            return fallback

    print("Default revision %s not found in %s. Bailing." % (default_revision, repo_name))
    print("Branches found:")
    for branch in [branch['name'] for branch in result]:
        print(branch)
    print("Use the ROOMSERVICE_BRANCHES environment variable to specify a list of fallback branches.")
    sys.exit()

if depsonly:
    repo_path = get_from_manifest(device)
    if repo_path:
        fetch_dependencies(repo_path)
    else:
        print("Trying dependencies-only mode on a non-existing device tree?")

    sys.exit()

else:
    for repository in repositories:
        repo_name = repository['name']
        if re.match(r"^android_device_[^_]*_" + device + "$", repo_name):
            print("Found repository: %s" % repository['name'])
            
            manufacturer = repo_name.replace("android_device_", "").replace("_" + device, "")
            repo_path = "device/%s/%s" % (manufacturer, device)
            revision = get_default_or_fallback_revision(repo_name)

            device_repository = {'repository':repo_name,'target_path':repo_path,'branch':revision}
            add_to_manifest([device_repository])

            print("Syncing repository to retrieve project.")
            os.system('repo sync --force-sync %s' % repo_path)
            print("Repository synced!")

            fetch_dependencies(repo_path)
            print("Done")
            sys.exit()

print("Repository for %s not found in the LineageOS Github repository list. If this is in error, you may need to manually add it to your local_manifests/roomservice.xml." % device)

再到根目录执行breakfast lemonades,此操作会创建.repo/local_manifests/内创建一个仓库列表文件,专门存储设备内核设置文件的仓库地址。这个时候如果出现错误,就请进行之前的获取设备元数据的操作。

12.开始编译

下载完成后执行以下:

# 原神 启动!
croot
brunch lemonades

最后编译起来,成功后:

13. 刷入编译的压缩包

首先进入recovery模式

adb reboot recovery

点击Apply Update,再执行命令:

adb sideload out/target/product/lemonades/lineage-21.0-20240704-UNOFFICIAL-lemonades.zip

执行完成等待烧录结果就可以了,安装时会提示签名认证未通过,不影响,直接安装

开机后显示的版本号也对了:

四. lineageOS历史版本(需fq)

这里因为官网上全是按照最新的版本,比如一加9r只有Android14,但我想搞Android13咋办呢,就找了个方式寻找一加历史版本:lineage-archive.timschumi.net/

更多历史版本可以进入:web.archive.org/

使用方式如下: 这个网站进去可以输入一个URL可以根据时间看到其更新历史,我输入的就是LineageOS一加9r版本的官网

我输入的就是LineageOS一加9r版本的官网download.lineageos.org/devices/lem…,点击确定

可以选择指定的日期时间后跳转到当时的旧版本网页,可以找到过去的版本

出现编译失败

执行brunch lemonades的时候出现问题

oneplus_dynamic_partitions_partition_listoneplus_dynamic_partitions_size的大小不匹配,顺着log找,最后修改了device/oneplus/lemonades/BoardConfig.mk中的BOARD_ONEPLUS_DYNAMIC_PARTITIONS_SIZElogoneplus_dynamic_partitions_partition_list的大小再进行重新编译就可以通过了


参考文章:

  1. 关于Lineageos最新分支编译的众多隐坑
  2. aosp 12/13/lineageos19.1 framework学习编译刷入小米手机,努比亚
  3. wiki.lineageos.org/devices/lem…