add command for fetching info about stock OS kernel from its repo
This commit is contained in:
parent
cfbfaf7010
commit
1ceb322463
2 changed files with 214 additions and 0 deletions
200
src/commands/get-kernel-info.ts
Normal file
200
src/commands/get-kernel-info.ts
Normal file
|
@ -0,0 +1,200 @@
|
|||
import { Command, flags } from '@oclif/command'
|
||||
import assert from 'assert'
|
||||
import chalk from 'chalk'
|
||||
import { existsSync, promises as fs } from 'fs'
|
||||
import { JSDOM } from 'jsdom'
|
||||
import path from 'path'
|
||||
import { DEVICE_CONFIG_FLAGS, loadDeviceConfigs } from '../config/device'
|
||||
import { DeviceImages, prepareFactoryImages } from '../frontend/source'
|
||||
|
||||
import { loadBuildIndex } from '../images/build-index'
|
||||
import { DeviceImage } from '../images/device-image'
|
||||
import { maybePlural } from '../util/cli'
|
||||
import { updateMultiMap } from '../util/data'
|
||||
import { GitLsRemote } from '../util/git'
|
||||
import { spawnAsync } from '../util/process'
|
||||
|
||||
export default class GetKernelInfo extends Command {
|
||||
static description = 'get info about stock OS kernel: version, tag, branch, etc'
|
||||
|
||||
static flags = {
|
||||
buildIds: flags.string({
|
||||
char: 'b',
|
||||
multiple: true,
|
||||
default: ['cur'],
|
||||
}),
|
||||
...DEVICE_CONFIG_FLAGS,
|
||||
}
|
||||
|
||||
async run() {
|
||||
let { flags } = this.parse(GetKernelInfo)
|
||||
let devices = await loadDeviceConfigs(flags.devices)
|
||||
let res = await prepareFactoryImages(await loadBuildIndex(), devices, flags.buildIds)
|
||||
|
||||
let commitMap = new Map<string, DeviceImages[]>()
|
||||
let commitVersionMap = new Map<string, string>()
|
||||
|
||||
let kernelRepoLsRemote = new Map<string, Promise<GitLsRemote>>()
|
||||
|
||||
let kernelInfos: [Promise<KernelInfo>, DeviceImages][] = []
|
||||
|
||||
for (let deviceImages of res.values()) {
|
||||
let kernelRepoUrl = deviceImages.factoryImage.deviceConfig.device.kernel_repo_url
|
||||
if (!kernelRepoLsRemote.has(kernelRepoUrl)) {
|
||||
kernelRepoLsRemote.set(kernelRepoUrl, GitLsRemote.get(kernelRepoUrl))
|
||||
}
|
||||
|
||||
let dirs = ['vendor_dlkm/lib/modules', 'vendor/lib/modules']
|
||||
for (let dir of dirs) {
|
||||
let dirPath = path.join(deviceImages.unpackedFactoryImageDir, dir)
|
||||
if (!existsSync(dirPath)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const dirents = await fs.readdir(dirPath, { withFileTypes: true })
|
||||
|
||||
let moduleFile = dirents.find(de => {
|
||||
return (
|
||||
de.name.endsWith('.ko') &&
|
||||
// has a different format of vermagic
|
||||
!de.name.startsWith('fips')
|
||||
)
|
||||
})!.name
|
||||
|
||||
kernelInfos.push([getKernelInfoFromModule(path.join(dirPath, moduleFile)), deviceImages])
|
||||
}
|
||||
}
|
||||
|
||||
for (let [kiPromise, imageSet] of kernelInfos) {
|
||||
let ki: KernelInfo = await kiPromise
|
||||
|
||||
let cur = commitVersionMap.get(ki.commit)
|
||||
if (cur !== undefined) {
|
||||
assert(cur === ki.version, ki.commit)
|
||||
} else {
|
||||
commitVersionMap.set(ki.commit, ki.version)
|
||||
}
|
||||
|
||||
updateMultiMap(commitMap, ki.commit, imageSet)
|
||||
}
|
||||
|
||||
let commitInfos: Promise<CommitInfo>[] = []
|
||||
for (let [commit, deviceImages] of commitMap) {
|
||||
let factory = deviceImages[0].factoryImage
|
||||
let kernelRepoUrl = factory.deviceConfig.device.kernel_repo_url
|
||||
commitInfos.push(getCommitInfo(kernelRepoUrl, kernelRepoLsRemote, commit))
|
||||
}
|
||||
|
||||
let logSeparator = false
|
||||
|
||||
for (let i of await Promise.all(commitInfos)) {
|
||||
if (logSeparator) {
|
||||
this.log('=================================================================================\n')
|
||||
} else {
|
||||
logSeparator = true
|
||||
}
|
||||
let factory = commitMap.get(i.commit)!.map(i => i.factoryImage)
|
||||
this.log(
|
||||
chalk.bold(`${i.commit} (${commitVersionMap.get(i.commit)}): `) + getDeviceBuildIds(factory, flags.buildIds),
|
||||
)
|
||||
|
||||
if (i.branches.length > 0) {
|
||||
this.log(`HEAD of branch${maybePlural(i.branches, '', 'es')}:\n${i.branches.join('\n')}`)
|
||||
}
|
||||
|
||||
if (i.tags.length > 0) {
|
||||
this.log(`\nTag${maybePlural(i.tags)}:`)
|
||||
} else {
|
||||
this.log(`No tag for ${i.commit}`)
|
||||
}
|
||||
|
||||
for (let tag of i.tags) {
|
||||
this.log(`${tag.name} | ${tag.annotation}\n${tag.date} | ${tag.author}\n`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface CommitInfo {
|
||||
commit: string
|
||||
branches: string[]
|
||||
tags: TagInfo[]
|
||||
}
|
||||
|
||||
interface TagInfo {
|
||||
name: string
|
||||
annotation: string
|
||||
date: string
|
||||
author: string
|
||||
}
|
||||
|
||||
interface KernelInfo {
|
||||
version: string
|
||||
commit: string
|
||||
}
|
||||
|
||||
async function getKernelInfoFromModule(modulePath: string) {
|
||||
let stdout = await spawnAsync('modinfo', ['--field=vermagic', modulePath])
|
||||
|
||||
let parts = stdout.split('-')
|
||||
// console.log(parts)
|
||||
let version = parts[0]
|
||||
|
||||
let commit: string
|
||||
if (parts[1].startsWith('android')) {
|
||||
commit = parts[3]
|
||||
} else {
|
||||
commit = parts[1]
|
||||
}
|
||||
assert(commit.length === 13)
|
||||
assert(commit[0] === 'g')
|
||||
commit = commit.substring(1)
|
||||
|
||||
return { version, commit } as KernelInfo
|
||||
}
|
||||
|
||||
async function getCommitInfo(repoUrl: string, lsRemoteMap: Map<string, Promise<GitLsRemote>>, commit: string) {
|
||||
let lsRemote = await lsRemoteMap.get(repoUrl)
|
||||
assert(lsRemote !== undefined)
|
||||
|
||||
let tags = lsRemote.getTagsForCommit(commit)
|
||||
let branches = lsRemote.getBranchesForCommit(commit)
|
||||
|
||||
let baseTagsUrl = repoUrl + '/+/refs/tags/'
|
||||
|
||||
let tagInfos = await Promise.all(tags.map(name => loadTagInfo(baseTagsUrl + name, name)))
|
||||
|
||||
return { commit, branches, tags: tagInfos } as CommitInfo
|
||||
}
|
||||
|
||||
async function loadTagInfo(gitilesUrl: string, name: string) {
|
||||
let resp = await fetch(gitilesUrl)
|
||||
assert(resp.ok)
|
||||
|
||||
// neither git ls-remote nor gitiles APIs support retrieving tag annotation, parse HTML
|
||||
|
||||
let jsdom = new JSDOM(await resp.text())
|
||||
let doc = jsdom.window.document
|
||||
|
||||
let tagAnnotation = doc.querySelector('body > div > div > pre:nth-child(3)') as HTMLPreElement
|
||||
assert(tagAnnotation.textContent!.endsWith('\n'))
|
||||
|
||||
let annotation = tagAnnotation.textContent!.slice(0, -1)
|
||||
|
||||
let tagger = doc.querySelector(
|
||||
'body > div > div > div:nth-child(2) > table > tbody > tr:nth-child(2)',
|
||||
) as HTMLTableRowElement
|
||||
|
||||
assert(tagger.cells[0].textContent === 'tagger')
|
||||
let date = tagger.cells[2].textContent
|
||||
let author = tagger.cells[1].textContent
|
||||
|
||||
return { name, annotation, date, author, } as TagInfo
|
||||
}
|
||||
|
||||
function getDeviceBuildIds(images: DeviceImage[], buildIdsFromFlags: string[]) {
|
||||
let hideBuildId = buildIdsFromFlags.length === 1 && buildIdsFromFlags[0] === 'cur'
|
||||
return hideBuildId
|
||||
? images.map(c => c.deviceConfig.device.name).join(' ')
|
||||
: images.map(c => `${c.deviceConfig.device.name} ${c.buildId}`).join(', ')
|
||||
}
|
|
@ -36,4 +36,18 @@ export class GitLsRemote {
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
public getTagsForCommit(commit: string) {
|
||||
return getKeysForCommit(this.tags, commit)
|
||||
}
|
||||
|
||||
public getBranchesForCommit(commit: string) {
|
||||
return getKeysForCommit(this.branches, commit)
|
||||
}
|
||||
}
|
||||
|
||||
function getKeysForCommit(map: Map<string, string>, value: string) {
|
||||
return Array.from(map.entries())
|
||||
.filter(entry => entry[1].startsWith(value))
|
||||
.map(entry => entry[0])
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue