Reformat all code with Prettier
This commit is contained in:
parent
d694cce8c5
commit
5a371055b2
35 changed files with 695 additions and 520 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -101,7 +101,7 @@ export function serializeVintfHals(hals: Array<VintfHal>) {
|
|||
type: 'device',
|
||||
},
|
||||
hal: hals,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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')))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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')))
|
||||
|
|
|
@ -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> = []
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import chalk from "chalk"
|
||||
import chalk from 'chalk'
|
||||
|
||||
export async function forEachDevice<Device>(
|
||||
devices: Device[],
|
||||
|
|
|
@ -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 }),
|
||||
}),
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue