Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: smart unpack for local module with dll #8645

Merged
merged 20 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/brave-snakes-unite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"app-builder-lib": patch
"builder-util": patch
---

fix: smart unpack for local module with dll
2 changes: 1 addition & 1 deletion packages/app-builder-lib/src/asar/asarUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export class AsarPackager {

for await (const fileSet of fileSets) {
if (this.config.options.smartUnpack !== false) {
detectUnpackedDirs(fileSet, unpackedPaths, this.config.defaultDestination)
detectUnpackedDirs(fileSet, unpackedPaths)
}

// Don't use BluebirdPromise, we need to retain order of execution/iteration through the ordered fileset
Expand Down
70 changes: 9 additions & 61 deletions packages/app-builder-lib/src/asar/unpackDetector.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,32 @@
import { log } from "builder-util"
import { log, FilterStats } from "builder-util"
import { isBinaryFileSync } from "isbinaryfile"
import * as path from "path"
import { NODE_MODULES_PATTERN } from "../fileTransformer"
import { getDestinationPath, ResolvedFileSet } from "../util/appFileCopier"

function addValue(map: Map<string, Array<string>>, key: string, value: string) {
let list = map.get(key)
if (list == null) {
list = [value]
map.set(key, list)
} else {
list.push(value)
}
}
import { ResolvedFileSet } from "../util/appFileCopier"

export function isLibOrExe(file: string): boolean {
// https://github.com/electron-userland/electron-builder/issues/3038
return file.endsWith(".dll") || file.endsWith(".exe") || file.endsWith(".dylib") || file.endsWith(".so") || file.endsWith(".node")
}

/** @internal */
export function detectUnpackedDirs(fileSet: ResolvedFileSet, autoUnpackDirs: Set<string>, defaultDestination: string) {
const dirToCreate = new Map<string, Array<string>>()
export function detectUnpackedDirs(fileSet: ResolvedFileSet, autoUnpackDirs: Set<string>) {
const metadata = fileSet.metadata

function addParents(child: string, root: string) {
child = path.dirname(child)
if (autoUnpackDirs.has(child)) {
return
}

do {
autoUnpackDirs.add(child)
const p = path.dirname(child)
// create parent dir to be able to copy file later without directory existence check
addValue(dirToCreate, p, path.basename(child))

if (child === root || p === root || autoUnpackDirs.has(p)) {
break
}
child = p
} while (true)

autoUnpackDirs.add(root)
}

for (let i = 0, n = fileSet.files.length; i < n; i++) {
const file = fileSet.files[i]
const index = file.lastIndexOf(NODE_MODULES_PATTERN)
if (index < 0) {
const stat: FilterStats = metadata.get(file)!
if (!stat.moduleRootPath || autoUnpackDirs.has(stat.moduleRootPath)) {
continue
}

let nextSlashIndex = file.indexOf(path.sep, index + NODE_MODULES_PATTERN.length + 1)
if (nextSlashIndex < 0) {
continue
}

if (file[index + NODE_MODULES_PATTERN.length] === "@") {
nextSlashIndex = file.indexOf(path.sep, nextSlashIndex + 1)
}

if (!metadata.get(file)!.isFile()) {
continue
}

const packageDir = file.substring(0, nextSlashIndex)
const packageDirPathInArchive = path.relative(defaultDestination, getDestinationPath(packageDir, fileSet))
const pathInArchive = path.relative(defaultDestination, getDestinationPath(file, fileSet))
if (autoUnpackDirs.has(packageDirPathInArchive)) {
// if package dir is unpacked, any file also unpacked
addParents(pathInArchive, packageDirPathInArchive)
if (!stat.isFile()) {
continue
}

// https://github.com/electron-userland/electron-builder/issues/2679
let shouldUnpack = false
// ffprobe-static and ffmpeg-static are known packages to always unpack
const moduleName = path.basename(packageDir)
const moduleName = stat.moduleName
const fileBaseName = path.basename(file)
const hasExtension = path.extname(fileBaseName)
if (moduleName === "ffprobe-static" || moduleName === "ffmpeg-static" || isLibOrExe(file)) {
Expand All @@ -91,9 +40,8 @@ export function detectUnpackedDirs(fileSet: ResolvedFileSet, autoUnpackDirs: Set
}

if (log.isDebugEnabled) {
log.debug({ file: pathInArchive, reason: "contains executable code" }, "not packed into asar archive")
log.debug({ file: stat.moduleFullFilePath, reason: "contains executable code" }, "not packed into asar archive")
}

addParents(pathInArchive, packageDirPathInArchive)
autoUnpackDirs.add(stat.moduleRootPath)
}
}
6 changes: 4 additions & 2 deletions packages/app-builder-lib/src/util/NodeModuleCopyHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class NodeModuleCopyHelper extends FileCopyHelper {
super(matcher, matcher.isEmpty() ? null : matcher.createFilter(), packager)
}

async collectNodeModules(moduleInfo: NodeModuleInfo, nodeModuleExcludedExts: Array<string>): Promise<Array<string>> {
async collectNodeModules(moduleInfo: NodeModuleInfo, nodeModuleExcludedExts: Array<string>, destination: string): Promise<Array<string>> {
const filter = this.filter
const metadata = this.metadata

Expand Down Expand Up @@ -114,7 +114,9 @@ export class NodeModuleCopyHelper extends FileCopyHelper {
}

return lstat(filePath).then((stat: FilterStats) => {
stat.relativeNodeModulesPath = path.join("node_modules", moduleName, path.relative(depPath, filePath))
stat.moduleName = moduleName
stat.moduleRootPath = destination
stat.moduleFullFilePath = path.join(destination, path.relative(depPath, filePath))
if (filter != null && !filter(filePath, stat)) {
return null
}
Expand Down
2 changes: 1 addition & 1 deletion packages/app-builder-lib/src/util/appFileCopier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export async function computeNodeModuleFileSets(platformPackager: PlatformPackag
const source = dep.dir
const matcher = new FileMatcher(source, destination, mainMatcher.macroExpander, mainMatcher.patterns)
const copier = new NodeModuleCopyHelper(matcher, platformPackager.info)
const files = await copier.collectNodeModules(dep, nodeModuleExcludedExts)
const files = await copier.collectNodeModules(dep, nodeModuleExcludedExts, path.relative(mainMatcher.to, destination))
result[index++] = validateFileSet({ src: source, destination, files, metadata: copier.metadata })

if (dep.conflictDependency) {
Expand Down
2 changes: 1 addition & 1 deletion packages/app-builder-lib/src/util/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function ensureEndSlash(s: string) {
}

function getRelativePath(file: string, srcWithEndSlash: string, stat: FilterStats) {
let relative = stat.relativeNodeModulesPath || file.substring(srcWithEndSlash.length)
let relative = stat.moduleFullFilePath || file.substring(srcWithEndSlash.length)
if (path.sep === "\\") {
if (relative.startsWith("\\")) {
// windows problem: double backslash, the above substring call removes root path with a single slash, so here can me some leftovers
Expand Down
14 changes: 11 additions & 3 deletions packages/builder-util/src/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,17 @@ export class CopyFileTransformer {

export type FileTransformer = (file: string) => Promise<null | string | Buffer | CopyFileTransformer> | null | string | Buffer | CopyFileTransformer
export interface FilterStats extends Stats {
// relative path of the dependency(node_modules + moduleName + file)
// Mainly used for filter, such as files filtering and asarUnpack filtering
relativeNodeModulesPath?: string
// These module name and paths are mainly used for:
// 1. File filtering
// 2. Asar unpacking rules
// 3. Dependency resolution

// The name of the node module (e.g. 'express')
moduleName?: string
// The root path of the node module (e.g. 'node_modules/express')
moduleRootPath?: string
// The full file path within the node module (e.g. 'node_modules/express/lib/application.js')
moduleFullFilePath?: string
// deal with asar unpack sysmlink
relativeLink?: string
linkRelativeToFile?: string
Expand Down
Loading
Loading