Reformat all code with Prettier

This commit is contained in:
Danny Lin 2021-12-25 01:47:33 -08:00
parent d694cce8c5
commit 5a371055b2
35 changed files with 695 additions and 520 deletions

View file

@ -56,8 +56,9 @@ async function listPayload(
// Get SELinux labels
let labels = await enumerateSelinuxLabels(mountpoint)
// Fix paths
labels = new Map(Array.from(labels.entries())
.map(([file, context]) => [`/${apexRoot}/${path.relative(mountpoint, file)}`, context]))
labels = new Map(
Array.from(labels.entries()).map(([file, context]) => [`/${apexRoot}/${path.relative(mountpoint, file)}`, context]),
)
return {
entries: entries,
@ -126,8 +127,7 @@ export async function flattenAllApexs(
// Flatten and add new entries
let apex = await flattenApex(entry.partition, apexPath, subTmp, progressCallback)
apex.entries.forEach(e => entries.add(e))
Array.from(apex.labels.entries())
.forEach(([path, context]) => labels.set(path, context))
Array.from(apex.labels.entries()).forEach(([path, context]) => labels.set(path, context))
// Remove the APEX blob entry
entries.delete(entry)

View file

@ -1,8 +1,30 @@
import path from 'path'
import { promises as fs } from 'fs'
import { blobToFileCopy, BoardMakefile, ModulesMakefile, DeviceMakefile, sanitizeBasename, serializeBoardMakefile, serializeModulesMakefile, serializeDeviceMakefile, Symlink, ProductsMakefile, ProductMakefile, serializeProductMakefile, serializeProductsMakefile } from '../build/make'
import { blobToSoongModule, serializeBlueprint, SharedLibraryModule, SoongBlueprint, SoongModule, SPECIAL_FILE_EXTENSIONS, TYPE_SHARED_LIBRARY } from '../build/soong'
import {
blobToFileCopy,
BoardMakefile,
ModulesMakefile,
DeviceMakefile,
sanitizeBasename,
serializeBoardMakefile,
serializeModulesMakefile,
serializeDeviceMakefile,
Symlink,
ProductsMakefile,
ProductMakefile,
serializeProductMakefile,
serializeProductsMakefile,
} from '../build/make'
import {
blobToSoongModule,
serializeBlueprint,
SharedLibraryModule,
SoongBlueprint,
SoongModule,
SPECIAL_FILE_EXTENSIONS,
TYPE_SHARED_LIBRARY,
} from '../build/soong'
import { BlobEntry, blobNeedsSoong } from './entry'
export interface BuildFiles {
@ -84,9 +106,11 @@ export async function generateBuild(
let needsMakeFallback = false
if (namedModules.has(name)) {
let conflictModule = namedModules.get(name)!
if (conflictModule._type == TYPE_SHARED_LIBRARY &&
(conflictModule as SharedLibraryModule).compile_multilib == 'both' &&
conflictModule._entry?.path.split('/').at(-1) == pathParts.at(-1)) {
if (
conflictModule._type == TYPE_SHARED_LIBRARY &&
(conflictModule as SharedLibraryModule).compile_multilib == 'both' &&
conflictModule._entry?.path.split('/').at(-1) == pathParts.at(-1)
) {
// Same partition = skip arch variant
if (conflictModule._entry?.partition == entry.partition) {
continue

View file

@ -20,7 +20,7 @@ export async function copyBlobs(entries: Iterable<BlobEntry>, srcDir: string, de
}
// Create directory structure
await fs.mkdir(path.dirname(outPath), {recursive: true})
await fs.mkdir(path.dirname(outPath), { recursive: true })
// Some files need patching
if (entry.path.endsWith('.xml')) {

View file

@ -60,8 +60,7 @@ export function blobNeedsSoong(entry: BlobEntry, ext: string) {
}
// Soong is also required for APKs, framework JARs, and vintf XMLs
if (ext == '.apk' || ext == '.jar' ||
(entry.path.startsWith('etc/vintf/') && ext == '.xml')) {
if (ext == '.apk' || ext == '.jar' || (entry.path.startsWith('etc/vintf/') && ext == '.xml')) {
return true
}

View file

@ -44,12 +44,12 @@ export async function listPart(
showSpinner: boolean = false,
) {
let partRoot = `${systemRoot}/${partition}`
if (!await exists(partRoot)) {
if (!(await exists(partRoot))) {
return null
}
// Unwrap system-as-root
if (partition == 'system' && await exists(`${partRoot}/system`)) {
if (partition == 'system' && (await exists(`${partRoot}/system`))) {
partRoot += '/system'
}
let refRoot = path.dirname(partRoot)

View file

@ -45,17 +45,19 @@ export type PartResValues = { [part: string]: ResValues }
function makeManifestRegex(attr: string) {
return new RegExp(
(/^\s+A: http:\/\/schemas.android.com\/apk\/res\/android:/).source +
attr +
(/\(0x[a-z0-9]+\)="(.+)" \(Raw: ".*$/).source,
'm' // multiline flag
/^\s+A: http:\/\/schemas.android.com\/apk\/res\/android:/.source +
attr +
/\(0x[a-z0-9]+\)="(.+)" \(Raw: ".*$/.source,
'm', // multiline flag
)
}
function encodeResKey(key: ResKey) {
// pkg/name:type/key|flags
return `${key.targetPkg}${key.targetName?.length ? `/${key.targetName}` : ''}:` +
return (
`${key.targetPkg}${key.targetName?.length ? `/${key.targetName}` : ''}:` +
`${key.type}/${key.key}${key.flags?.length ? `|${key.flags}` : ''}`
)
}
export function decodeResKey(encoded: string) {
@ -66,7 +68,7 @@ export function decodeResKey(encoded: string) {
return {
targetPkg: targetPkg,
targetName: targetName != undefined ? targetName: null,
targetName: targetName != undefined ? targetName : null,
type: type,
key: key,
flags: flags != undefined ? flags : null,
@ -327,8 +329,7 @@ function filterValues(keyFilters: Filters, valueFilters: Filters, values: ResVal
for (let [rawKey, value] of values.entries()) {
let key = decodeResKey(rawKey)
if (shouldDeleteKey(keyFilters, rawKey, key) ||
(typeof value == 'string' && !filterValue(valueFilters, value))) {
if (shouldDeleteKey(keyFilters, rawKey, key) || (typeof value == 'string' && !filterValue(valueFilters, value))) {
// Key/value filter
values.delete(rawKey)
} else if (DIFF_MAP_PACKAGES.has(key.targetPkg)) {
@ -402,15 +403,17 @@ export async function serializePartOverlays(partValues: PartResValues, overlaysD
let rroName = `${genTarget}.auto_generated_rro_${partition}_adevtool__`
let bp = serializeBlueprint({
modules: [{
_type: 'runtime_resource_overlay',
name: rroName,
modules: [
{
_type: 'runtime_resource_overlay',
name: rroName,
...(partition == 'system_ext' && { system_ext_specific: true }),
...(partition == 'product' && { product_specific: true }),
...(partition == 'vendor' && { soc_specific: true }),
...(partition == 'odm' && { device_specific: true }),
}],
...(partition == 'system_ext' && { system_ext_specific: true }),
...(partition == 'product' && { product_specific: true }),
...(partition == 'vendor' && { soc_specific: true }),
...(partition == 'odm' && { device_specific: true }),
},
],
})
let manifest = serializeXmlObject({
@ -429,12 +432,12 @@ export async function serializePartOverlays(partValues: PartResValues, overlaysD
},
},
],
application: [ { $: { 'android:hasCode': 'false' } } ],
application: [{ $: { 'android:hasCode': 'false' } }],
},
})
let valuesObj = { resources: { } as { [type: string]: Array<any> } }
for (let [{type, key}, value] of values.entries()) {
let valuesObj = { resources: {} as { [type: string]: Array<any> } }
for (let [{ type, key }, value] of values.entries()) {
let entry = {
$: {
name: key,

View file

@ -19,8 +19,7 @@ export async function parsePresignedRecursive(sepolicyDirs: Array<string>) {
}
}
return new Set(contexts.filter(c => c.seinfo != 'platform')
.map(c => c.name))
return new Set(contexts.filter(c => c.seinfo != 'platform').map(c => c.name))
}
async function getPkgName(aapt2Path: string, apkPath: string) {
@ -45,8 +44,10 @@ export async function updatePresignedBlobs(
entryCallback(entry)
}
if ((filters != null && filterValue(filters, entry.srcPath)) ||
presignedPkgs.has(await getPkgName(aapt2Path, `${source}/${entry.srcPath}`))) {
if (
(filters != null && filterValue(filters, entry.srcPath)) ||
presignedPkgs.has(await getPkgName(aapt2Path, `${source}/${entry.srcPath}`))
) {
entry.isPresigned = true
updatedEntries.push(entry)
}
@ -55,11 +56,7 @@ export async function updatePresignedBlobs(
return updatedEntries
}
export async function enumeratePresignedBlobs(
aapt2Path: string,
source: string,
presignedPkgs: Set<string>,
) {
export async function enumeratePresignedBlobs(aapt2Path: string, source: string, presignedPkgs: Set<string>) {
let presignedPaths = []
for await (let file of listFilesRecursive(source)) {
if (path.extname(file) != '.apk') {

View file

@ -101,7 +101,7 @@ export function serializeVintfHals(hals: Array<VintfHal>) {
type: 'device',
},
hal: hals,
}
},
})
}

View file

@ -95,10 +95,7 @@ export function blobToFileCopy(entry: BlobEntry, proprietaryDir: string) {
export function serializeModulesMakefile(mk: ModulesMakefile) {
let blocks = startBlocks()
blocks.push(
'LOCAL_PATH := $(call my-dir)',
`ifeq ($(TARGET_DEVICE),${mk.device})`,
)
blocks.push('LOCAL_PATH := $(call my-dir)', `ifeq ($(TARGET_DEVICE),${mk.device})`)
if (mk.radioFiles != undefined) {
blocks.push(mk.radioFiles.map(img => `$(call add-radio-file,${img})`).join('\n'))
@ -169,15 +166,14 @@ TARGET_COPY_OUT_ODM_DLKM := odm_dlkm`)
}
if (mk.sepolicyResolutions != undefined) {
for (let [partition, {sepolicyDirs, missingContexts}] of mk.sepolicyResolutions.entries()) {
for (let [partition, { sepolicyDirs, missingContexts }] of mk.sepolicyResolutions.entries()) {
let partVar = SEPOLICY_PARTITION_VARS[partition]
if (sepolicyDirs.length > 0) {
addContBlock(blocks, partVar, sepolicyDirs)
}
if (missingContexts.length > 0) {
blocks.push(missingContexts.map(c => `# Missing ${partition} SELinux context: ${c}`)
.join('\n'))
blocks.push(missingContexts.map(c => `# Missing ${partition} SELinux context: ${c}`).join('\n'))
}
}
}
@ -244,7 +240,11 @@ PRODUCT_MANUFACTURER := ${mk.manufacturer}`)
export function serializeProductsMakefile(mk: ProductsMakefile) {
let blocks = [MAKEFILE_HEADER]
addContBlock(blocks, 'PRODUCT_MAKEFILES', mk.products.map(p => `$(LOCAL_DIR)/${p}.mk`))
addContBlock(
blocks,
'PRODUCT_MAKEFILES',
mk.products.map(p => `$(LOCAL_DIR)/${p}.mk`),
)
return finishBlocks(blocks)
}

View file

@ -19,11 +19,7 @@ export interface TargetModuleInfo {
export type SoongModuleInfo = Map<string, TargetModuleInfo>
const EXCLUDE_MODULE_CLASSES = new Set([
"NATIVE_TESTS",
"FAKE",
"ROBOLECTRIC",
])
const EXCLUDE_MODULE_CLASSES = new Set(['NATIVE_TESTS', 'FAKE', 'ROBOLECTRIC'])
export function parseModuleInfo(info: string) {
return new Map(Object.entries(JSON.parse(info))) as SoongModuleInfo

View file

@ -3,13 +3,7 @@ import util from 'util'
import { BlobEntry, partPathToSrcPath } from '../blobs/entry'
import { SOONG_HEADER } from '../util/headers'
export const SPECIAL_FILE_EXTENSIONS = new Set([
'.so',
'.apk',
'.jar',
'.xml',
'.apex',
])
export const SPECIAL_FILE_EXTENSIONS = new Set(['.so', '.apk', '.jar', '.xml', '.apex'])
export const TYPE_SHARED_LIBRARY = 'cc_prebuilt_library_shared'
@ -89,24 +83,23 @@ export interface RroModule {
theme?: string
}
export interface SoongNamespace {
}
export interface SoongNamespace {}
export type SoongModuleSpecific = {
// This is used initially, but deleted before serialization
_type?: string
} & (
SharedLibraryModule |
ExecutableModule |
ScriptModule |
ApkModule |
ApexModule |
JarModule |
EtcModule |
EtcXmlModule |
DspModule |
SoongNamespace |
RroModule
| SharedLibraryModule
| ExecutableModule
| ScriptModule
| ApkModule
| ApexModule
| JarModule
| EtcModule
| EtcXmlModule
| DspModule
| SoongNamespace
| RroModule
)
export type SoongModule = {
@ -151,7 +144,8 @@ export function blobToSoongModule(
// Type and info is based on file extension
let moduleSpecific: SoongModuleSpecific
// High-precedence extension-based types first
if (ext == '.sh') { // check before bin/ to catch .sh files in bin
if (ext == '.sh') {
// check before bin/ to catch .sh files in bin
let relPath = getRelativeInstallPath(entry, pathParts, 'bin')
moduleSpecific = {
@ -168,7 +162,7 @@ export function blobToSoongModule(
filename_from_src: true,
...(relPath && { sub_dir: relPath }),
}
// Then special paths
// Then special paths
} else if (pathParts[0] == 'bin') {
let relPath = getRelativeInstallPath(entry, pathParts, 'bin')
@ -201,7 +195,7 @@ export function blobToSoongModule(
filename_from_src: true,
...(relPath && { sub_dir: relPath }),
}
// Then other extension-based types
// Then other extension-based types
} else if (ext == '.so') {
// Extract architecture from lib dir
let libDir = pathParts.at(0)!
@ -234,12 +228,18 @@ export function blobToSoongModule(
} as TargetSrcs
// For multi-arch
let targetSrcs32 = (curArch == '32') ? targetSrcs : {
srcs: [otherSrcPath],
} as TargetSrcs
let targetSrcs64 = (curArch == '64') ? targetSrcs : {
srcs: [otherSrcPath],
} as TargetSrcs
let targetSrcs32 =
curArch == '32'
? targetSrcs
: ({
srcs: [otherSrcPath],
} as TargetSrcs)
let targetSrcs64 =
curArch == '64'
? targetSrcs
: ({
srcs: [otherSrcPath],
} as TargetSrcs)
let origFileName = pathParts.at(-1)?.replace(/\.so$/, '')
moduleSpecific = {
@ -265,7 +265,7 @@ export function blobToSoongModule(
moduleSpecific = {
_type: 'android_app_import',
apk: entry.srcPath,
...(entry.isPresigned && { presigned: true } || { certificate: 'platform' }),
...((entry.isPresigned && { presigned: true }) || { certificate: 'platform' }),
...(entry.path.startsWith('priv-app/') && { privileged: true }),
dex_preopt: {
enabled: false,
@ -301,35 +301,35 @@ export function blobToSoongModule(
}
export function serializeModule(module: SoongModule) {
// Type is prepended to Soong module props, so remove it from the object
let type = module._type
delete module._type
// Type is prepended to Soong module props, so remove it from the object
let type = module._type
delete module._type
// Delete internal blob entry reference as well
delete module._entry
// Delete internal blob entry reference as well
delete module._entry
// Initial serialization pass. Node.js util.inspect happens to be very similar to Soong format.
let serialized = util.inspect(module, {
depth: Infinity,
maxArrayLength: Infinity,
maxStringLength: Infinity,
breakLength: 100,
})
// Initial serialization pass. Node.js util.inspect happens to be very similar to Soong format.
let serialized = util.inspect(module, {
depth: Infinity,
maxArrayLength: Infinity,
maxStringLength: Infinity,
breakLength: 100,
})
// ' -> "
serialized = serialized.replaceAll("'", '"')
// 4-space indentation
serialized = serialized.replaceAll(' ', ' ')
// Prepend type
serialized = `${type} ${serialized}`
// Add trailing comma to last prop
let serialLines = serialized.split('\n')
if (serialLines.length > 1) {
serialLines[serialLines.length - 2] = serialLines.at(-2) + ','
serialized = serialLines.join('\n')
}
// ' -> "
serialized = serialized.replaceAll("'", '"')
// 4-space indentation
serialized = serialized.replaceAll(' ', ' ')
// Prepend type
serialized = `${type} ${serialized}`
// Add trailing comma to last prop
let serialLines = serialized.split('\n')
if (serialLines.length > 1) {
serialLines[serialLines.length - 2] = serialLines.at(-2) + ','
serialized = serialLines.join('\n')
}
return serialized
return serialized
}
export function serializeBlueprint(bp: SoongBlueprint) {
@ -337,9 +337,11 @@ export function serializeBlueprint(bp: SoongBlueprint) {
// Declare namespace
if (bp.namespace) {
serializedModules.push(serializeModule({
_type: 'soong_namespace',
}))
serializedModules.push(
serializeModule({
_type: 'soong_namespace',
}),
)
}
if (bp.modules != undefined) {

View file

@ -10,19 +10,31 @@ export default class CheckPresigned extends Command {
static description = 'check for APKs that should be presigned'
static flags = {
help: flags.help({char: 'h'}),
aapt2: flags.string({char: 'a', description: 'path to aapt2 executable', default: 'out/host/linux-x86/bin/aapt2'}),
sepolicy: flags.string({char: 'p', description: 'paths to device and vendor sepolicy dirs', required: true, multiple: true}),
outList: flags.string({char: 'o', description: 'output path for new proprietary-files.txt with PRESIGNED tags'}),
help: flags.help({ char: 'h' }),
aapt2: flags.string({
char: 'a',
description: 'path to aapt2 executable',
default: 'out/host/linux-x86/bin/aapt2',
}),
sepolicy: flags.string({
char: 'p',
description: 'paths to device and vendor sepolicy dirs',
required: true,
multiple: true,
}),
outList: flags.string({ char: 'o', description: 'output path for new proprietary-files.txt with PRESIGNED tags' }),
}
static args = [
{name: 'source', description: 'path to mounted factory images', required: true},
{name: 'listPath', description: 'path to LineageOS-compatible proprietary-files.txt list'},
{ name: 'source', description: 'path to mounted factory images', required: true },
{ name: 'listPath', description: 'path to LineageOS-compatible proprietary-files.txt list' },
]
async run() {
let {flags: {aapt2: aapt2Path, sepolicy: sepolicyDirs, outList: outPath}, args: {source, listPath}} = this.parse(CheckPresigned)
let {
flags: { aapt2: aapt2Path, sepolicy: sepolicyDirs, outList: outPath },
args: { source, listPath },
} = this.parse(CheckPresigned)
// Parse list
this.log(chalk.bold(chalk.greenBright('Parsing list')))

View file

@ -8,27 +8,31 @@ export default class CollectState extends Command {
static description = 'collect built system state for use with other commands'
static flags = {
help: flags.help({char: 'h'}),
aapt2: flags.string({char: 'a', description: 'path to aapt2 executable', default: 'out/host/linux-x86/bin/aapt2'}),
device: flags.string({char: 'd', description: 'name of target device', required: true, multiple: true}),
outRoot: flags.string({char: 'r', description: 'path to AOSP build output directory (out/)', default: 'out'}),
parallel: flags.boolean({char: 'p', description: 'generate devices in parallel (causes buggy progress spinners)', default: false}),
help: flags.help({ char: 'h' }),
aapt2: flags.string({
char: 'a',
description: 'path to aapt2 executable',
default: 'out/host/linux-x86/bin/aapt2',
}),
device: flags.string({ char: 'd', description: 'name of target device', required: true, multiple: true }),
outRoot: flags.string({ char: 'r', description: 'path to AOSP build output directory (out/)', default: 'out' }),
parallel: flags.boolean({
char: 'p',
description: 'generate devices in parallel (causes buggy progress spinners)',
default: false,
}),
}
static args = [
{name: 'output_path', description: 'output path for system state JSON file(s)', required: true},
]
static args = [{ name: 'output_path', description: 'output path for system state JSON file(s)', required: true }]
async run() {
let {flags: {
aapt2: aapt2Path,
device: devices,
outRoot,
parallel,
}, args: {output_path: outPath}} = this.parse(CollectState)
let {
flags: { aapt2: aapt2Path, device: devices, outRoot, parallel },
args: { output_path: outPath },
} = this.parse(CollectState)
let isDir = (await fs.stat(outPath)).isDirectory()
await forEachDevice(devices, parallel, async (device) => {
await forEachDevice(devices, parallel, async device => {
let state = await collectSystemState(device, outRoot, aapt2Path)
// Write

View file

@ -8,17 +8,24 @@ export default class DiffFiles extends Command {
static description = 'find missing system files compared to a reference system'
static flags = {
help: flags.help({char: 'h'}),
all: flags.boolean({char: 'a', description: 'show all differences, not only missing/removed files', default: false})
help: flags.help({ char: 'h' }),
all: flags.boolean({
char: 'a',
description: 'show all differences, not only missing/removed files',
default: false,
}),
}
static args = [
{name: 'sourceRef', description: 'path to root of reference system', required: true},
{name: 'sourceNew', description: 'path to root of new system', required: true},
{ name: 'sourceRef', description: 'path to root of reference system', required: true },
{ name: 'sourceNew', description: 'path to root of new system', required: true },
]
async run() {
let {flags: {all}, args: {sourceRef, sourceNew}} = this.parse(DiffFiles)
let {
flags: { all },
args: { sourceRef, sourceNew },
} = this.parse(DiffFiles)
for (let partition of ALL_SYS_PARTITIONS) {
let filesRef = await listPart(partition, sourceRef)
@ -26,7 +33,7 @@ export default class DiffFiles extends Command {
continue
}
let filesNew = await listPart(partition, sourceNew) ?? []
let filesNew = (await listPart(partition, sourceNew)) ?? []
this.log(chalk.bold(partition))

View file

@ -21,10 +21,7 @@ function forEachPropLine(props: Map<string, string>, callback: (prop: string) =>
}
}
function forEachPropLineModified(
props: Map<string, Array<string>>,
callback: (prop: string) => void,
) {
function forEachPropLineModified(props: Map<string, Array<string>>, callback: (prop: string) => void) {
for (let [key, [refValue, newValue]] of props.entries()) {
callback(`${key}=${chalk.bold(refValue)} -> ${chalk.bold(chalk.blue(newValue))}`)
}
@ -34,18 +31,21 @@ export default class DiffProps extends Command {
static description = 'find missing and different properties compared to a reference system'
static flags = {
help: flags.help({char: 'h'}),
all: flags.boolean({char: 'a', description: 'show all differences, not only missing props', default: false}),
includeBuild: flags.boolean({char: 'b', description: 'include build props', default: false}),
help: flags.help({ char: 'h' }),
all: flags.boolean({ char: 'a', description: 'show all differences, not only missing props', default: false }),
includeBuild: flags.boolean({ char: 'b', description: 'include build props', default: false }),
}
static args = [
{name: 'sourceRef', description: 'path to root of reference system', required: true},
{name: 'sourceNew', description: 'path to root of new system', required: true},
{ name: 'sourceRef', description: 'path to root of reference system', required: true },
{ name: 'sourceNew', description: 'path to root of new system', required: true },
]
async run() {
let {flags: {all, includeBuild}, args: {sourceRef, sourceNew}} = this.parse(DiffProps)
let {
flags: { all, includeBuild },
args: { sourceRef, sourceNew },
} = this.parse(DiffProps)
let propsRef = await loadPartitionProps(sourceRef)
let propsNew = await loadPartitionProps(sourceNew)

View file

@ -9,18 +9,25 @@ export default class DiffVintf extends Command {
static description = 'find missing vintf declarations compared to a reference system'
static flags = {
help: flags.help({char: 'h'}),
all: flags.boolean({char: 'a', description: 'show all differences, not only missing/removed HALs', default: false})
help: flags.help({ char: 'h' }),
all: flags.boolean({
char: 'a',
description: 'show all differences, not only missing/removed HALs',
default: false,
}),
}
static args = [
{name: 'sourceRef', description: 'path to root of reference system', required: true},
{name: 'sourceNew', description: 'path to root of new system', required: true},
{name: 'outPath', description: 'output path for manifest fragment with missing HALs'},
{ name: 'sourceRef', description: 'path to root of reference system', required: true },
{ name: 'sourceNew', description: 'path to root of new system', required: true },
{ name: 'outPath', description: 'output path for manifest fragment with missing HALs' },
]
async run() {
let {flags: {all}, args: {sourceRef, sourceNew, outPath}} = this.parse(DiffVintf)
let {
flags: { all },
args: { sourceRef, sourceNew, outPath },
} = this.parse(DiffVintf)
let vintfRef = await loadPartVintfInfo(sourceRef)
let vintfNew = await loadPartVintfInfo(sourceNew)

View file

@ -4,35 +4,55 @@ import chalk from 'chalk'
import { downloadFile, ImageType, IndexCache } from '../images/download'
const IMAGE_TYPE_MAP: { [type: string]: ImageType } = {
'factory': ImageType.Factory,
'ota': ImageType.Ota,
'vendor': ImageType.Vendor,
factory: ImageType.Factory,
ota: ImageType.Ota,
vendor: ImageType.Vendor,
}
export default class Download extends Command {
static description = 'download device factory images, OTAs, and/or vendor packages'
static flags = {
help: flags.help({char: 'h'}),
type: flags.string({char: 't', options: ['factory', 'ota', 'vendor'], description: 'type(s) of images to download', default: ['factory'], multiple: true}),
buildId: flags.string({char: 'b', description: 'build ID(s) of the images to download', required: true, multiple: true, default: ['latest']}),
device: flags.string({char: 'd', description: 'device(s) to download images for', required: true, multiple: true}),
help: flags.help({ char: 'h' }),
type: flags.string({
char: 't',
options: ['factory', 'ota', 'vendor'],
description: 'type(s) of images to download',
default: ['factory'],
multiple: true,
}),
buildId: flags.string({
char: 'b',
description: 'build ID(s) of the images to download',
required: true,
multiple: true,
default: ['latest'],
}),
device: flags.string({
char: 'd',
description: 'device(s) to download images for',
required: true,
multiple: true,
}),
}
static args = [
{name: 'out', description: 'directory to save downloaded files in', required: true},
]
static args = [{ name: 'out', description: 'directory to save downloaded files in', required: true }]
async run() {
let {flags, args: {out}} = this.parse(Download)
let {
flags,
args: { out },
} = this.parse(Download)
await fs.mkdir(out, { recursive: true })
this.log(chalk.bold(chalk.yellowBright("By downloading images, you agree to Google's terms and conditions:")))
this.log(chalk.yellow(` - https://developers.google.com/android/images#legal
this.log(
chalk.yellow(` - https://developers.google.com/android/images#legal
- https://developers.google.com/android/ota#legal
- https://policies.google.com/terms
`))
`),
)
let cache: IndexCache = {}
for (let device of flags.device) {

View file

@ -10,19 +10,22 @@ export default class Extract extends Command {
static description = 'extract proprietary files'
static flags = {
help: flags.help({char: 'h'}),
vendor: flags.string({char: 'v', description: 'device vendor/OEM name', required: true}),
device: flags.string({char: 'd', description: 'device codename', required: true}),
skipCopy: flags.boolean({char: 'k', description: 'skip file copying and only generate build files'}),
help: flags.help({ char: 'h' }),
vendor: flags.string({ char: 'v', description: 'device vendor/OEM name', required: true }),
device: flags.string({ char: 'd', description: 'device codename', required: true }),
skipCopy: flags.boolean({ char: 'k', description: 'skip file copying and only generate build files' }),
}
static args = [
{name: 'source', description: 'path to mounted factory images', required: true},
{name: 'listPath', description: 'path to LineageOS-compatible proprietary-files.txt list', required: true},
{ name: 'source', description: 'path to mounted factory images', required: true },
{ name: 'listPath', description: 'path to LineageOS-compatible proprietary-files.txt list', required: true },
]
async run() {
let {args: {source, listPath}, flags: {vendor, device, skipCopy}} = this.parse(Extract)
let {
args: { source, listPath },
flags: { vendor, device, skipCopy },
} = this.parse(Extract)
// Parse list
this.log(chalk.bold(chalk.greenBright('Parsing list')))

View file

@ -1,7 +1,15 @@
import { Command, flags } from '@oclif/command'
import { wrapSystemSrc } from '../frontend/source'
import { KeyInfo, MacSigner, readKeysConfRecursive, readMacPermissionsRecursive, readPartMacPermissions, resolveKeys, writeMappedKeys } from '../selinux/keys'
import {
KeyInfo,
MacSigner,
readKeysConfRecursive,
readMacPermissionsRecursive,
readPartMacPermissions,
resolveKeys,
writeMappedKeys,
} from '../selinux/keys'
import { withSpinner } from '../util/cli'
import { withTempDir } from '../util/fs'
@ -9,22 +17,42 @@ export default class FixCerts extends Command {
static description = 'fix SELinux presigned app certificates'
static flags = {
help: flags.help({char: 'h'}),
sepolicy: flags.string({char: 'p', description: 'paths to device and vendor sepolicy dirs', required: true, multiple: true}),
device: flags.string({char: 'd', description: 'device codename', required: true}),
buildId: flags.string({char: 'b', description: 'build ID of the stock images (optional, only used for locating factory images)'}),
stockSrc: flags.string({char: 's', description: 'path to (extracted) factory images, (mounted) images, (extracted) OTA package, OTA payload, or directory containing any such files (optionally under device and/or build ID directory)', required: true}),
useTemp: flags.boolean({char: 't', description: 'use a temporary directory for all extraction (prevents reusing extracted files across runs)', default: false}),
help: flags.help({ char: 'h' }),
sepolicy: flags.string({
char: 'p',
description: 'paths to device and vendor sepolicy dirs',
required: true,
multiple: true,
}),
device: flags.string({ char: 'd', description: 'device codename', required: true }),
buildId: flags.string({
char: 'b',
description: 'build ID of the stock images (optional, only used for locating factory images)',
}),
stockSrc: flags.string({
char: 's',
description:
'path to (extracted) factory images, (mounted) images, (extracted) OTA package, OTA payload, or directory containing any such files (optionally under device and/or build ID directory)',
required: true,
}),
useTemp: flags.boolean({
char: 't',
description: 'use a temporary directory for all extraction (prevents reusing extracted files across runs)',
default: false,
}),
}
async run() {
let {flags: {sepolicy: sepolicyDirs, device, buildId, stockSrc, useTemp}} = this.parse(FixCerts)
let {
flags: { sepolicy: sepolicyDirs, device, buildId, stockSrc, useTemp },
} = this.parse(FixCerts)
await withTempDir(async (tmp) => {
await withTempDir(async tmp => {
// Prepare stock system source
let wrapBuildId = buildId == undefined ? null : buildId
let wrapped = await withSpinner('Extracting stock system source', (spinner) =>
wrapSystemSrc(stockSrc, device, wrapBuildId, useTemp, tmp, spinner))
let wrapped = await withSpinner('Extracting stock system source', spinner =>
wrapSystemSrc(stockSrc, device, wrapBuildId, useTemp, tmp, spinner),
)
stockSrc = wrapped.src!
let srcSigners: Array<MacSigner> = []

View file

@ -5,7 +5,20 @@ import { copyBlobs } from '../blobs/copy'
import { BlobEntry } from '../blobs/entry'
import { DeviceConfig, loadDeviceConfigs } from '../config/device'
import { forEachDevice } from '../frontend/devices'
import { enumerateFiles, extractFirmware, extractOverlays, extractProps, extractVintfManifests, flattenApexs, generateBuildFiles, loadCustomState, PropResults, resolveOverrides, resolveSepolicyDirs, updatePresigned } from '../frontend/generate'
import {
enumerateFiles,
extractFirmware,
extractOverlays,
extractProps,
extractVintfManifests,
flattenApexs,
generateBuildFiles,
loadCustomState,
PropResults,
resolveOverrides,
resolveSepolicyDirs,
updatePresigned,
} from '../frontend/generate'
import { wrapSystemSrc } from '../frontend/source'
import { SelinuxPartResolutions } from '../selinux/contexts'
import { withSpinner } from '../util/cli'
@ -20,139 +33,181 @@ const doDevice = (
factoryPath: string | undefined,
skipCopy: boolean,
useTemp: boolean,
) => withTempDir(async (tmp) => {
// Prepare stock system source
let wrapBuildId = buildId == undefined ? null : buildId
let wrapped = await withSpinner('Extracting stock system source', (spinner) =>
wrapSystemSrc(stockSrc, config.device.name, wrapBuildId, useTemp, tmp, spinner))
stockSrc = wrapped.src!
if (wrapped.factoryPath != null && factoryPath == undefined) {
factoryPath = wrapped.factoryPath
}
// customSrc can point to a (directory containing) system state JSON or out/
let customState = await loadCustomState(config, aapt2Path, customSrc)
// Each step will modify this. Key = combined part path
let namedEntries = new Map<string, BlobEntry>()
// Prepare output directories
let dirs = await createVendorDirs(config.device.vendor, config.device.name)
// 1. Diff files
await withSpinner('Enumerating files', (spinner) =>
enumerateFiles(spinner, config.filters.files, config.filters.dep_files,
namedEntries, customState, stockSrc))
// 2. Overrides
let buildPkgs: string[] = []
if (config.generate.overrides) {
let builtModules = await withSpinner('Replacing blobs with buildable modules', () =>
resolveOverrides(config, customState, dirs, namedEntries))
buildPkgs.push(...builtModules)
}
// After this point, we only need entry objects
let entries = Array.from(namedEntries.values())
// 3. Presigned
if (config.generate.presigned) {
await withSpinner('Marking apps as presigned', (spinner) =>
updatePresigned(spinner, config, entries, aapt2Path, stockSrc))
}
// 4. Flatten APEX modules
if (config.generate.flat_apex) {
entries = await withSpinner('Flattening APEX modules', (spinner) =>
flattenApexs(spinner, entries, dirs, tmp, stockSrc))
}
// 5. Extract
// Copy blobs (this has its own spinner)
if (config.generate.files && !skipCopy) {
await copyBlobs(entries, stockSrc, dirs.proprietary)
}
// 6. Props
let propResults: PropResults | null = null
if (config.generate.props) {
propResults = await withSpinner('Extracting properties', () =>
extractProps(config, customState, stockSrc))
}
// 7. SELinux policies
let sepolicyResolutions: SelinuxPartResolutions | null = null
if (config.generate.sepolicy_dirs) {
sepolicyResolutions = await withSpinner('Adding missing SELinux policies', () =>
resolveSepolicyDirs(config, customState, dirs, stockSrc))
}
// 8. Overlays
if (config.generate.overlays) {
let overlayPkgs = await withSpinner('Extracting overlays', (spinner) =>
extractOverlays(spinner, config, customState, dirs, aapt2Path, stockSrc))
buildPkgs.push(...overlayPkgs)
}
// 9. vintf manifests
let vintfManifestPaths: Map<string, string> | null = null
if (config.generate.vintf) {
vintfManifestPaths = await withSpinner('Extracting vintf manifests', () =>
extractVintfManifests(customState, dirs, stockSrc))
}
// 10. Firmware
let fwPaths: Array<string> | null = null
if (config.generate.factory_firmware && factoryPath != undefined) {
if (propResults == null) {
throw new Error('Factory firmware extraction depends on properties')
) =>
withTempDir(async tmp => {
// Prepare stock system source
let wrapBuildId = buildId == undefined ? null : buildId
let wrapped = await withSpinner('Extracting stock system source', spinner =>
wrapSystemSrc(stockSrc, config.device.name, wrapBuildId, useTemp, tmp, spinner),
)
stockSrc = wrapped.src!
if (wrapped.factoryPath != null && factoryPath == undefined) {
factoryPath = wrapped.factoryPath
}
fwPaths = await withSpinner('Extracting firmware', () =>
extractFirmware(config, dirs, propResults!.stockProps, factoryPath!))
}
// customSrc can point to a (directory containing) system state JSON or out/
let customState = await loadCustomState(config, aapt2Path, customSrc)
// 11. Build files
await withSpinner('Generating build files', () =>
generateBuildFiles(config, dirs, entries, buildPkgs, propResults, fwPaths,
vintfManifestPaths, sepolicyResolutions, stockSrc))
})
// Each step will modify this. Key = combined part path
let namedEntries = new Map<string, BlobEntry>()
// Prepare output directories
let dirs = await createVendorDirs(config.device.vendor, config.device.name)
// 1. Diff files
await withSpinner('Enumerating files', spinner =>
enumerateFiles(spinner, config.filters.files, config.filters.dep_files, namedEntries, customState, stockSrc),
)
// 2. Overrides
let buildPkgs: string[] = []
if (config.generate.overrides) {
let builtModules = await withSpinner('Replacing blobs with buildable modules', () =>
resolveOverrides(config, customState, dirs, namedEntries),
)
buildPkgs.push(...builtModules)
}
// After this point, we only need entry objects
let entries = Array.from(namedEntries.values())
// 3. Presigned
if (config.generate.presigned) {
await withSpinner('Marking apps as presigned', spinner =>
updatePresigned(spinner, config, entries, aapt2Path, stockSrc),
)
}
// 4. Flatten APEX modules
if (config.generate.flat_apex) {
entries = await withSpinner('Flattening APEX modules', spinner =>
flattenApexs(spinner, entries, dirs, tmp, stockSrc),
)
}
// 5. Extract
// Copy blobs (this has its own spinner)
if (config.generate.files && !skipCopy) {
await copyBlobs(entries, stockSrc, dirs.proprietary)
}
// 6. Props
let propResults: PropResults | null = null
if (config.generate.props) {
propResults = await withSpinner('Extracting properties', () => extractProps(config, customState, stockSrc))
}
// 7. SELinux policies
let sepolicyResolutions: SelinuxPartResolutions | null = null
if (config.generate.sepolicy_dirs) {
sepolicyResolutions = await withSpinner('Adding missing SELinux policies', () =>
resolveSepolicyDirs(config, customState, dirs, stockSrc),
)
}
// 8. Overlays
if (config.generate.overlays) {
let overlayPkgs = await withSpinner('Extracting overlays', spinner =>
extractOverlays(spinner, config, customState, dirs, aapt2Path, stockSrc),
)
buildPkgs.push(...overlayPkgs)
}
// 9. vintf manifests
let vintfManifestPaths: Map<string, string> | null = null
if (config.generate.vintf) {
vintfManifestPaths = await withSpinner('Extracting vintf manifests', () =>
extractVintfManifests(customState, dirs, stockSrc),
)
}
// 10. Firmware
let fwPaths: Array<string> | null = null
if (config.generate.factory_firmware && factoryPath != undefined) {
if (propResults == null) {
throw new Error('Factory firmware extraction depends on properties')
}
fwPaths = await withSpinner('Extracting firmware', () =>
extractFirmware(config, dirs, propResults!.stockProps, factoryPath!),
)
}
// 11. Build files
await withSpinner('Generating build files', () =>
generateBuildFiles(
config,
dirs,
entries,
buildPkgs,
propResults,
fwPaths,
vintfManifestPaths,
sepolicyResolutions,
stockSrc,
),
)
})
export default class GenerateFull extends Command {
static description = 'generate all vendor parts automatically'
static flags = {
help: flags.help({char: 'h'}),
aapt2: flags.string({char: 'a', description: 'path to aapt2 executable', default: 'out/host/linux-x86/bin/aapt2'}),
buildId: flags.string({char: 'b', description: 'build ID of the stock images'}),
stockSrc: flags.string({char: 's', description: 'path to (extracted) factory images, (mounted) images, (extracted) OTA package, OTA payload, or directory containing any such files (optionally under device and/or build ID directory)', required: true}),
customSrc: flags.string({char: 'c', description: 'path to AOSP build output directory (out/) or (directory containing) JSON state file', default: 'out'}),
factoryPath: flags.string({char: 'f', description: 'path to stock factory images zip (for extracting firmware if stockSrc is not factory images)'}),
skipCopy: flags.boolean({char: 'k', description: 'skip file copying and only generate build files', default: false}),
useTemp: flags.boolean({char: 't', description: 'use a temporary directory for all extraction (prevents reusing extracted files across runs)', default: false}),
parallel: flags.boolean({char: 'p', description: 'generate devices in parallel (causes buggy progress spinners)', default: false}),
help: flags.help({ char: 'h' }),
aapt2: flags.string({
char: 'a',
description: 'path to aapt2 executable',
default: 'out/host/linux-x86/bin/aapt2',
}),
buildId: flags.string({ char: 'b', description: 'build ID of the stock images' }),
stockSrc: flags.string({
char: 's',
description:
'path to (extracted) factory images, (mounted) images, (extracted) OTA package, OTA payload, or directory containing any such files (optionally under device and/or build ID directory)',
required: true,
}),
customSrc: flags.string({
char: 'c',
description: 'path to AOSP build output directory (out/) or (directory containing) JSON state file',
default: 'out',
}),
factoryPath: flags.string({
char: 'f',
description: 'path to stock factory images zip (for extracting firmware if stockSrc is not factory images)',
}),
skipCopy: flags.boolean({
char: 'k',
description: 'skip file copying and only generate build files',
default: false,
}),
useTemp: flags.boolean({
char: 't',
description: 'use a temporary directory for all extraction (prevents reusing extracted files across runs)',
default: false,
}),
parallel: flags.boolean({
char: 'p',
description: 'generate devices in parallel (causes buggy progress spinners)',
default: false,
}),
}
static args = [
{name: 'config', description: 'path to device-specific YAML config', required: true},
]
static args = [{ name: 'config', description: 'path to device-specific YAML config', required: true }]
async run() {
let {flags: {
aapt2: aapt2Path,
buildId,
stockSrc,
customSrc,
factoryPath,
skipCopy,
useTemp,
parallel,
}, args: {config: configPath}} = this.parse(GenerateFull)
let {
flags: { aapt2: aapt2Path, buildId, stockSrc, customSrc, factoryPath, skipCopy, useTemp, parallel },
args: { config: configPath },
} = this.parse(GenerateFull)
let devices = await loadDeviceConfigs(configPath)
await forEachDevice(devices, parallel, async (config) => {
await doDevice(config, stockSrc, customSrc, aapt2Path, buildId,
factoryPath, skipCopy, useTemp)
}, config => config.device.name)
await forEachDevice(
devices,
parallel,
async config => {
await doDevice(config, stockSrc, customSrc, aapt2Path, buildId, factoryPath, skipCopy, useTemp)
},
config => config.device.name,
)
}
}

View file

@ -16,77 +16,95 @@ const doDevice = (
buildId: string | undefined,
skipCopy: boolean,
useTemp: boolean,
) => withTempDir(async (tmp) => {
// Prepare stock system source
let wrapBuildId = buildId == undefined ? null : buildId
let wrapped = await withSpinner('Extracting stock system source', (spinner) =>
wrapSystemSrc(stockSrc, config.device.name, wrapBuildId, useTemp, tmp, spinner))
stockSrc = wrapped.src!
) =>
withTempDir(async tmp => {
// Prepare stock system source
let wrapBuildId = buildId == undefined ? null : buildId
let wrapped = await withSpinner('Extracting stock system source', spinner =>
wrapSystemSrc(stockSrc, config.device.name, wrapBuildId, useTemp, tmp, spinner),
)
stockSrc = wrapped.src!
// Each step will modify this. Key = combined part path
let namedEntries = new Map<string, BlobEntry>()
// Each step will modify this. Key = combined part path
let namedEntries = new Map<string, BlobEntry>()
// Prepare output directories
let dirs = await createVendorDirs(config.device.vendor, config.device.name)
// Prepare output directories
let dirs = await createVendorDirs(config.device.vendor, config.device.name)
// 1. Diff files
await withSpinner('Enumerating files', (spinner) =>
enumerateFiles(spinner, config.filters.dep_files, null, namedEntries, null,
stockSrc))
// 1. Diff files
await withSpinner('Enumerating files', spinner =>
enumerateFiles(spinner, config.filters.dep_files, null, namedEntries, null, stockSrc),
)
// After this point, we only need entry objects
let entries = Array.from(namedEntries.values())
// After this point, we only need entry objects
let entries = Array.from(namedEntries.values())
// 2. Extract
// Copy blobs (this has its own spinner)
if (config.generate.files && !skipCopy) {
await copyBlobs(entries, stockSrc, dirs.proprietary)
}
// 2. Extract
// Copy blobs (this has its own spinner)
if (config.generate.files && !skipCopy) {
await copyBlobs(entries, stockSrc, dirs.proprietary)
}
// 3. Props
let propResults: PropResults | null = null
if (config.generate.props) {
propResults = await withSpinner('Extracting properties', () =>
extractProps(config, null, stockSrc))
delete propResults.missingProps
delete propResults.fingerprint
}
// 3. Props
let propResults: PropResults | null = null
if (config.generate.props) {
propResults = await withSpinner('Extracting properties', () => extractProps(config, null, stockSrc))
delete propResults.missingProps
delete propResults.fingerprint
}
// 4. Build files
await withSpinner('Generating build files', () =>
generateBuildFiles(config, dirs, entries, [], propResults, null, null, null,
stockSrc, false, true))
})
// 4. Build files
await withSpinner('Generating build files', () =>
generateBuildFiles(config, dirs, entries, [], propResults, null, null, null, stockSrc, false, true),
)
})
export default class GeneratePrep extends Command {
static description = 'generate vendor parts to prepare for reference AOSP build (e.g. for collect-state)'
static flags = {
help: flags.help({char: 'h'}),
buildId: flags.string({char: 'b', description: 'build ID of the stock images'}),
stockSrc: flags.string({char: 's', description: 'path to (extracted) factory images, (mounted) images, (extracted) OTA package, OTA payload, or directory containing any such files (optionally under device and/or build ID directory)', required: true}),
skipCopy: flags.boolean({char: 'k', description: 'skip file copying and only generate build files', default: false}),
useTemp: flags.boolean({char: 't', description: 'use a temporary directory for all extraction (prevents reusing extracted files across runs)', default: false}),
parallel: flags.boolean({char: 'p', description: 'generate devices in parallel (causes buggy progress spinners)', default: false}),
help: flags.help({ char: 'h' }),
buildId: flags.string({ char: 'b', description: 'build ID of the stock images' }),
stockSrc: flags.string({
char: 's',
description:
'path to (extracted) factory images, (mounted) images, (extracted) OTA package, OTA payload, or directory containing any such files (optionally under device and/or build ID directory)',
required: true,
}),
skipCopy: flags.boolean({
char: 'k',
description: 'skip file copying and only generate build files',
default: false,
}),
useTemp: flags.boolean({
char: 't',
description: 'use a temporary directory for all extraction (prevents reusing extracted files across runs)',
default: false,
}),
parallel: flags.boolean({
char: 'p',
description: 'generate devices in parallel (causes buggy progress spinners)',
default: false,
}),
}
static args = [
{name: 'config', description: 'path to device-specific YAML config', required: true},
]
static args = [{ name: 'config', description: 'path to device-specific YAML config', required: true }]
async run() {
let {flags: {
buildId,
stockSrc,
skipCopy,
useTemp,
parallel,
}, args: {config: configPath}} = this.parse(GeneratePrep)
let {
flags: { buildId, stockSrc, skipCopy, useTemp, parallel },
args: { config: configPath },
} = this.parse(GeneratePrep)
let devices = await loadDeviceConfigs(configPath)
await forEachDevice(devices, parallel, async (config) => {
await doDevice(config, stockSrc, buildId, skipCopy, useTemp)
}, config => config.device.name)
await forEachDevice(
devices,
parallel,
async config => {
await doDevice(config, stockSrc, buildId, skipCopy, useTemp)
},
config => config.device.name,
)
}
}

View file

@ -8,16 +8,22 @@ export default class ListFiles extends Command {
static description = 'list system files and symlinks important for blobs'
static flags = {
help: flags.help({char: 'h'}),
help: flags.help({ char: 'h' }),
}
static args = [
{name: 'systemRoot', description: 'path to root of mounted system images (./system_ext, ./product, etc.)', required: true},
{name: 'out', description: 'directory to write partition file lists to', required: true},
{
name: 'systemRoot',
description: 'path to root of mounted system images (./system_ext, ./product, etc.)',
required: true,
},
{ name: 'out', description: 'directory to write partition file lists to', required: true },
]
async run() {
let {args: {systemRoot, out}} = this.parse(ListFiles)
let {
args: { systemRoot, out },
} = this.parse(ListFiles)
await fs.mkdir(out, { recursive: true })

View file

@ -9,28 +9,38 @@ export default class ResolveOverrides extends Command {
static description = 'resolve packages to build from a list of overridden targets'
static flags = {
help: flags.help({char: 'h'}),
help: flags.help({ char: 'h' }),
}
static args = [
{name: 'overrideList', description: 'path to root of mounted system images (./system_ext, ./product, etc.)', required: true},
{name: 'moduleInfo', description: 'path to Soong module-info.json (out/target/product/$device/module-info.json)', required: true},
{
name: 'overrideList',
description: 'path to root of mounted system images (./system_ext, ./product, etc.)',
required: true,
},
{
name: 'moduleInfo',
description: 'path to Soong module-info.json (out/target/product/$device/module-info.json)',
required: true,
},
]
async run() {
let {args: {overrideList: listPath, moduleInfo}} = this.parse(ResolveOverrides)
let {
args: { overrideList: listPath, moduleInfo },
} = this.parse(ResolveOverrides)
let overridesList = await readFile(listPath)
let overrides = parseOverrides(overridesList)
let modulesMap = parseModuleInfo(await readFile(moduleInfo))
let {modules, missingPaths} = findOverrideModules(overrides, modulesMap)
let { modules, missingPaths } = findOverrideModules(overrides, modulesMap)
let makefile = serializeDeviceMakefile({
packages: modules,
})
let missing = missingPaths.length == 0 ? '' : '\n\n# Missing paths:\n' +
missingPaths.map(path => `# ${path}`).join('\n')
let missing =
missingPaths.length == 0 ? '' : '\n\n# Missing paths:\n' + missingPaths.map(path => `# ${path}`).join('\n')
this.log(makefile + missing)
}

View file

@ -145,8 +145,9 @@ async function loadAndMergeConfig(configPath: string) {
let merged = overlays.reduce((base, overlay) => mergeConfigs(base, overlay), base)
// Parse filters
merged.filters = Object.fromEntries(Object.entries(merged.filters)
.map(([group, filters]) => [group, parseFilters(filters as SerializedFilters)]))
merged.filters = Object.fromEntries(
Object.entries(merged.filters).map(([group, filters]) => [group, parseFilters(filters as SerializedFilters)]),
)
// Finally, cast it to the parsed config type
delete merged.includes

View file

@ -41,17 +41,17 @@ export function parseFilters(src: SerializedFilters) {
}
function _matchFilters(filters: Filters, value: string) {
return filters.match.has(value) ||
return (
filters.match.has(value) ||
filters.prefix.find(prefix => value.startsWith(prefix)) != undefined ||
filters.suffix.find(suffix => value.endsWith(suffix)) != undefined ||
filters.substring.find(substring => value.includes(substring)) != undefined ||
filters.regex.find(regex => value.match(regex)) != null
)
}
export function filterValue(filters: Filters, value: string) {
return filters.include ?
_matchFilters(filters, value) :
!_matchFilters(filters, value)
return filters.include ? _matchFilters(filters, value) : !_matchFilters(filters, value)
}
export function filterValues(filters: Filters, values: string[]) {
@ -71,6 +71,5 @@ export function filterKeys<Value>(filters: Filters, map: Map<string, Value>) {
// Map, copy
export function filterKeysCopy<Value>(filters: Filters, map: Map<string, Value>) {
return new Map(Array.from(map.entries())
.filter(([key]) => filterValue(filters, key)))
return new Map(Array.from(map.entries()).filter(([key]) => filterValue(filters, key)))
}

View file

@ -1,12 +1,12 @@
import { listPart } from "../blobs/file-list"
import { parsePartOverlayApks, PartResValues } from "../blobs/overlays"
import { loadPartitionProps, PartitionProps } from "../blobs/props"
import { loadPartVintfInfo, PartitionVintfInfo } from "../blobs/vintf"
import { minimizeModules, parseModuleInfo, SoongModuleInfo } from "../build/soong-info"
import { parsePartContexts, SelinuxPartContexts } from "../selinux/contexts"
import { withSpinner } from "../util/cli"
import { readFile } from "../util/fs"
import { ALL_SYS_PARTITIONS } from "../util/partitions"
import { listPart } from '../blobs/file-list'
import { parsePartOverlayApks, PartResValues } from '../blobs/overlays'
import { loadPartitionProps, PartitionProps } from '../blobs/props'
import { loadPartVintfInfo, PartitionVintfInfo } from '../blobs/vintf'
import { minimizeModules, parseModuleInfo, SoongModuleInfo } from '../build/soong-info'
import { parsePartContexts, SelinuxPartContexts } from '../selinux/contexts'
import { withSpinner } from '../util/cli'
import { readFile } from '../util/fs'
import { ALL_SYS_PARTITIONS } from '../util/partitions'
const STATE_VERSION = 4
@ -36,16 +36,20 @@ export function serializeSystemState(state: SystemState) {
...state,
}
return JSON.stringify(diskState, (k, v) => {
if (v instanceof Map) {
return {
_type: 'Map',
data: Object.fromEntries(v.entries()),
return JSON.stringify(
diskState,
(k, v) => {
if (v instanceof Map) {
return {
_type: 'Map',
data: Object.fromEntries(v.entries()),
}
} else {
return v
}
} else {
return v
}
}, 2)
},
2,
)
}
export function parseSystemState(json: string) {
@ -75,7 +79,7 @@ export async function collectSystemState(device: string, outRoot: string, aapt2P
} as SystemState
// Files
await withSpinner('Enumerating files', async (spinner) => {
await withSpinner('Enumerating files', async spinner => {
for (let partition of ALL_SYS_PARTITIONS) {
spinner.text = partition
@ -87,26 +91,25 @@ export async function collectSystemState(device: string, outRoot: string, aapt2P
})
// Props
state.partitionProps = await withSpinner('Extracting properties', () =>
loadPartitionProps(systemRoot))
state.partitionProps = await withSpinner('Extracting properties', () => loadPartitionProps(systemRoot))
// SELinux contexts
state.partitionSecontexts = await withSpinner('Extracting SELinux contexts', () =>
parsePartContexts(systemRoot))
state.partitionSecontexts = await withSpinner('Extracting SELinux contexts', () => parsePartContexts(systemRoot))
// Overlays
state.partitionOverlays = await withSpinner('Extracting overlays', (spinner) =>
state.partitionOverlays = await withSpinner('Extracting overlays', spinner =>
parsePartOverlayApks(aapt2Path, systemRoot, path => {
spinner.text = path
}))
}),
)
// vintf info
state.partitionVintfInfo = await withSpinner('Extracting vintf manifests', () =>
loadPartVintfInfo(systemRoot))
state.partitionVintfInfo = await withSpinner('Extracting vintf manifests', () => loadPartVintfInfo(systemRoot))
// Module info
state.moduleInfo = await withSpinner('Parsing module info', async () =>
parseModuleInfo(await readFile(moduleInfoPath)))
parseModuleInfo(await readFile(moduleInfoPath)),
)
return state;
return state
}

View file

@ -1,4 +1,4 @@
import chalk from "chalk"
import chalk from 'chalk'
export async function forEachDevice<Device>(
devices: Device[],

View file

@ -16,7 +16,14 @@ import { DeviceConfig } from '../config/device'
import { filterKeys, Filters, filterValue, filterValues } from '../config/filters'
import { collectSystemState, parseSystemState, SystemState } from '../config/system-state'
import { ANDROID_INFO, extractFactoryFirmware, generateAndroidInfo, writeFirmwareImages } from '../images/firmware'
import { diffPartContexts, parseContextsRecursive, parsePartContexts, resolvePartContextDiffs, SelinuxContexts, SelinuxPartResolutions } from '../selinux/contexts'
import {
diffPartContexts,
parseContextsRecursive,
parsePartContexts,
resolvePartContextDiffs,
SelinuxContexts,
SelinuxPartResolutions,
} from '../selinux/contexts'
import { generateFileContexts } from '../selinux/labels'
import { exists, readFile, TempState } from '../util/fs'
import { ALL_SYS_PARTITIONS } from '../util/partitions'
@ -29,11 +36,7 @@ export interface PropResults {
missingOtaParts: Array<string>
}
export async function loadCustomState(
config: DeviceConfig,
aapt2Path: string,
customSrc: string,
) {
export async function loadCustomState(config: DeviceConfig, aapt2Path: string, customSrc: string) {
if ((await fs.stat(customSrc)).isFile()) {
return parseSystemState(await readFile(customSrc))
} else {
@ -96,7 +99,7 @@ export async function resolveOverrides(
let modulesMap = customState.moduleInfo
removeSelfModules(modulesMap, dirs.proprietary)
let {modules: builtModules, builtPaths} = findOverrideModules(targetPaths, modulesMap)
let { modules: builtModules, builtPaths } = findOverrideModules(targetPaths, modulesMap)
// Remove new modules from entries
for (let path of builtPaths) {
@ -114,9 +117,16 @@ export async function updatePresigned(
stockSrc: string,
) {
let presignedPkgs = await parsePresignedRecursive(config.platform.sepolicy_dirs)
await updatePresignedBlobs(aapt2Path, stockSrc, presignedPkgs, entries, entry => {
spinner.text = entry.srcPath
}, config.filters.presigned)
await updatePresignedBlobs(
aapt2Path,
stockSrc,
presignedPkgs,
entries,
entry => {
spinner.text = entry.srcPath
},
config.filters.presigned,
)
}
export async function flattenApexs(
@ -126,7 +136,7 @@ export async function flattenApexs(
tmp: TempState,
stockSrc: string,
) {
let apex = await flattenAllApexs(entries, stockSrc, tmp, (progress) => {
let apex = await flattenAllApexs(entries, stockSrc, tmp, progress => {
spinner.text = progress
})
@ -137,11 +147,7 @@ export async function flattenApexs(
return apex.entries
}
export async function extractProps(
config: DeviceConfig,
customState: SystemState | null,
stockSrc: string,
) {
export async function extractProps(config: DeviceConfig, customState: SystemState | null, stockSrc: string) {
let stockProps = await loadPartitionProps(stockSrc)
let customProps = customState?.partitionProps ?? new Map<string, Map<string, string>>()
@ -160,15 +166,13 @@ export async function extractProps(
let missingProps: PartitionProps | undefined = undefined
if (customProps != null) {
let propChanges = diffPartitionProps(stockProps, customProps)
missingProps = new Map(Array.from(propChanges.entries())
.map(([part, props]) => [part, props.removed]))
missingProps = new Map(Array.from(propChanges.entries()).map(([part, props]) => [part, props.removed]))
}
// A/B OTA partitions
let stockOtaParts = stockProps.get('product')!.get('ro.product.ab_ota_partitions')!.split(',')
let customOtaParts = new Set(customProps.get('product')?.get('ro.product.ab_ota_partitions')?.split(',') ?? [])
let missingOtaParts = stockOtaParts.filter(p => !customOtaParts.has(p) &&
filterValue(config.filters.partitions, p))
let missingOtaParts = stockOtaParts.filter(p => !customOtaParts.has(p) && filterValue(config.filters.partitions, p))
return {
stockProps,
@ -219,13 +223,22 @@ export async function extractOverlays(
aapt2Path: string,
stockSrc: string,
) {
let stockOverlays = await parsePartOverlayApks(aapt2Path, stockSrc, path => {
spinner.text = path
}, config.filters.overlay_files)
let stockOverlays = await parsePartOverlayApks(
aapt2Path,
stockSrc,
path => {
spinner.text = path
},
config.filters.overlay_files,
)
let customOverlays = customState.partitionOverlays
let missingOverlays = diffPartOverlays(stockOverlays, customOverlays,
config.filters.overlay_keys, config.filters.overlay_values)
let missingOverlays = diffPartOverlays(
stockOverlays,
customOverlays,
config.filters.overlay_keys,
config.filters.overlay_values,
)
// Generate RROs and get a list of modules to build
let buildPkgs = await serializePartOverlays(missingOverlays, dirs.overlays)
@ -243,11 +256,7 @@ export async function extractOverlays(
return buildPkgs
}
export async function extractVintfManifests(
customState: SystemState,
dirs: VendorDirectories,
stockSrc: string,
) {
export async function extractVintfManifests(customState: SystemState, dirs: VendorDirectories, stockSrc: string) {
let customVintf = customState.partitionVintfInfo
let stockVintf = await loadPartVintfInfo(stockSrc)
let missingHals = diffPartVintfManifests(customVintf, stockVintf)
@ -305,8 +314,8 @@ export async function generateBuildFiles(
// Add board parts
build.boardMakefile = {
...(sepolicyResolutions != null && { sepolicyResolutions: sepolicyResolutions }),
...(propResults != null && propResults.missingOtaParts.length > 0 &&
{
...(propResults != null &&
propResults.missingOtaParts.length > 0 && {
buildPartitions: propResults.missingOtaParts,
...(addAbOtaParts && { abOtaPartitions: propResults.missingOtaParts }),
}),

View file

@ -55,17 +55,11 @@ class SourceResolver {
}
}
private async mountImg(
img: string,
dest: string,
) {
private async mountImg(img: string, dest: string) {
// Convert sparse image to raw
if (await isSparseImage(img)) {
this.spinner.text = `converting sparse image: ${img}`
let sparseTmp = await this.createDynamicTmp(
`sparse_img/${path.basename(path.dirname(img))}`,
path.dirname(img),
)
let sparseTmp = await this.createDynamicTmp(`sparse_img/${path.basename(path.dirname(img))}`, path.dirname(img))
let rawImg = `${sparseTmp.dir}/${path.basename(img)}.raw`
await run(`simg2img ${img} ${rawImg}`)
@ -78,11 +72,7 @@ class SourceResolver {
this.tmp.mounts.push(dest)
}
private async mountParts(
src: string,
mountTmp: TempState,
suffix: string = '.img',
) {
private async mountParts(src: string, mountTmp: TempState, suffix: string = '.img') {
let mountRoot = mountTmp.dir
for (let part of ALL_SYS_PARTITIONS) {
@ -95,14 +85,8 @@ class SourceResolver {
}
}
private async wrapLeafFile(
file: string,
factoryPath: string | null,
): Promise<WrappedSource> {
let imagesTmp = await this.createDynamicTmp(
`src_images/${path.basename(file)}`,
path.dirname(file),
)
private async wrapLeafFile(file: string, factoryPath: string | null): Promise<WrappedSource> {
let imagesTmp = await this.createDynamicTmp(`src_images/${path.basename(file)}`, path.dirname(file))
// Extract images from OTA payload
if (path.basename(file) == 'payload.bin') {
@ -151,10 +135,7 @@ class SourceResolver {
}
}
private async searchLeafDir(
src: string,
factoryPath: string | null,
): Promise<WrappedSource> {
private async searchLeafDir(src: string, factoryPath: string | null): Promise<WrappedSource> {
if (!(await exists(src))) {
return {
src: null,
@ -186,8 +167,7 @@ class SourceResolver {
return await this.wrapLeafFile(imagesZip, factoryPath || src)
}
let newFactoryPath = (await fs.readdir(src))
.find(f => f.startsWith(`${this.device}-${this.buildId}-factory-`))
let newFactoryPath = (await fs.readdir(src)).find(f => f.startsWith(`${this.device}-${this.buildId}-factory-`))
if (newFactoryPath != undefined) {
// Factory images zip
return await this.wrapLeafFile(`${src}/${newFactoryPath}`, newFactoryPath)
@ -206,21 +186,19 @@ class SourceResolver {
// Directory
let tryDirs = [
...(this.buildId != null && [
...((this.buildId != null && [
`${src}/${this.buildId}`,
`${src}/${this.device}/${this.buildId}`,
`${src}/${this.buildId}/${this.device}`,
] || []),
]) ||
[]),
`${src}/${this.device}`,
src,
]
// Also try to find extracted factory images first: device-buildId
if (this.buildId != null) {
tryDirs = [
...tryDirs.map(p => `${p}/${this.device}-${this.buildId}`),
...tryDirs,
]
tryDirs = [...tryDirs.map(p => `${p}/${this.device}-${this.buildId}`), ...tryDirs]
}
for (let dir of tryDirs) {

View file

@ -52,7 +52,8 @@ async function getUrl(type: ImageType, buildId: string, device: string, cache: I
cache[type] = index
}
let filePrefix = filePattern.replace('DEVICE', device)
let filePrefix = filePattern
.replace('DEVICE', device)
.replace('BUILDID', buildId == 'latest' ? '' : buildId.toLowerCase() + '-')
let urlPrefix = DL_URL_PREFIX + filePrefix
@ -63,9 +64,7 @@ async function getUrl(type: ImageType, buildId: string, device: string, cache: I
}
if (buildId == 'latest') {
return matches
.map(m => m[1])
.sort((a, b) => b.localeCompare(a))[0]
return matches.map(m => m[1]).sort((a, b) => b.localeCompare(a))[0]
} else {
return matches[0][1]
}
@ -87,9 +86,12 @@ export async function downloadFile(
throw new Error(`Error ${resp.status}: ${resp.statusText}`)
}
let bar = new cliProgress.SingleBar({
format: ' {bar} {percentage}% | {value}/{total} MB',
}, cliProgress.Presets.shades_classic)
let bar = new cliProgress.SingleBar(
{
format: ' {bar} {percentage}% | {value}/{total} MB',
},
cliProgress.Presets.shades_classic,
)
let progress = 0
let totalSize = parseInt(resp.headers.get('content-length') ?? '0') / 1e6
bar.start(Math.round(totalSize), 0)

View file

@ -11,8 +11,7 @@ const CONTEXT_FILENAMES = new Set([
// Plain TYPE_contexts for AOSP sources
...CONTEXT_TYPES.map(type => `${type}_contexts`),
// PART_TYPE_contexts for built systems
...CONTEXT_TYPES.flatMap(type => Array.from(EXT_PARTITIONS.values())
.map(part => `${part}_${type}_contexts`)),
...CONTEXT_TYPES.flatMap(type => Array.from(EXT_PARTITIONS.values()).map(part => `${part}_${type}_contexts`)),
// Special case for vendor
'vndservice_contexts',
])
@ -60,8 +59,7 @@ export async function parsePartContexts(root: string) {
}
function diffContexts(ctxRef: SelinuxContexts, ctxNew: SelinuxContexts) {
return new Map(Array.from(ctxNew.entries())
.filter(([ctx]) => !ctxRef.has(ctx)))
return new Map(Array.from(ctxNew.entries()).filter(([ctx]) => !ctxRef.has(ctx)))
}
export function diffPartContexts(pctxRef: SelinuxPartContexts, pctxNew: SelinuxPartContexts) {

View file

@ -30,7 +30,14 @@ async function parseMacPermissions(xml: string) {
let signers = []
if (doc.policy) {
for (let { $: { signature: rawSig }, seinfo: [{ $: { value: seinfoId }}]} of doc.policy.signer) {
for (let {
$: { signature: rawSig },
seinfo: [
{
$: { value: seinfoId },
},
],
} of doc.policy.signer) {
// Parse base64 cert or leave it as a reference
let cert = rawSig.startsWith('@') ? rawSig.slice(1) : parseHex(rawSig)
signers.push({
@ -132,13 +139,16 @@ export function resolveKeys(
let keyToPaths = new Map(srcKeys.map(k => [k.keyId, Array.from(k.certPaths.values())]))
// Build seinfo -> paths map
let seinfoToPaths = new Map(srcMacPerms.filter(s => typeof s.cert == 'string')
.map(s => [s.seinfoId, keyToPaths.get(s.cert as string)!]))
let seinfoToPaths = new Map(
srcMacPerms.filter(s => typeof s.cert == 'string').map(s => [s.seinfoId, keyToPaths.get(s.cert as string)!]),
)
// Build cert -> paths map
return new Map(compiledMacPerms
.filter(s => seinfoToPaths.has(s.seinfoId) && s.cert instanceof Uint8Array)
.map(s => [s.cert as Uint8Array, seinfoToPaths.get(s.seinfoId)!]))
return new Map(
compiledMacPerms
.filter(s => seinfoToPaths.has(s.seinfoId) && s.cert instanceof Uint8Array)
.map(s => [s.cert as Uint8Array, seinfoToPaths.get(s.seinfoId)!]),
)
}
function serializeCert(cert: Uint8Array, lineLength: number) {

View file

@ -30,7 +30,9 @@ export async function enumerateSelinuxLabels(root: string) {
}
export function generateFileContexts(labels: SelinuxFileLabels) {
return Array.from(labels.entries())
.map(([path, context]) => `${_.escapeRegExp(path)} ${context}`)
.join('\n') + '\n'
return (
Array.from(labels.entries())
.map(([path, context]) => `${_.escapeRegExp(path)} ${context}`)
.join('\n') + '\n'
)
}

View file

@ -18,10 +18,7 @@ export function stopActionSpinner(spinner: ora.Ora) {
spinner.stopAndPersist()
}
export async function withSpinner<Return>(
action: string,
callback: (spinner: ora.Ora) => Promise<Return>,
) {
export async function withSpinner<Return>(action: string, callback: (spinner: ora.Ora) => Promise<Return>) {
let spinner = startActionSpinner(action)
let ret = await callback(spinner)
stopActionSpinner(spinner)

View file

@ -21,30 +21,15 @@ export enum Partition {
}
// Android system partitions, excluding "system"
export type ExtSysPartition = Partition.SystemExt |
Partition.Product |
Partition.Vendor |
Partition.Odm
export const EXT_SYS_PARTITIONS = new Set([
'system_ext',
'product',
'vendor',
'odm',
])
export type ExtSysPartition = Partition.SystemExt | Partition.Product | Partition.Vendor | Partition.Odm
export const EXT_SYS_PARTITIONS = new Set(['system_ext', 'product', 'vendor', 'odm'])
// GKI DLKM partitions
export type DlkmPartition = Partition.VendorDlkm |
Partition.OdmDlkm
export const DLKM_PARTITIONS = new Set([
'vendor_dlkm',
'odm_dlkm',
])
export type DlkmPartition = Partition.VendorDlkm | Partition.OdmDlkm
export const DLKM_PARTITIONS = new Set(['vendor_dlkm', 'odm_dlkm'])
export type ExtPartition = ExtSysPartition | DlkmPartition
export const EXT_PARTITIONS = new Set([
...EXT_SYS_PARTITIONS,
...DLKM_PARTITIONS,
])
export const EXT_PARTITIONS = new Set([...EXT_SYS_PARTITIONS, ...DLKM_PARTITIONS])
// All system partitions
export type SysPartition = Partition.System | ExtPartition