Skip to content

Commit f4d40f9

Browse files
authored
fix: smart unpack for local module with dll (electron-userland#8645)
Fix the following case: the foo module contains dll files that need to be packaged into asapUnpack ```javascript test.ifDevOrWinCi("smart unpack local module with dll file", () => { return app( { targets: Platform.WINDOWS.createTarget(DIR_TARGET), }, { isInstallDepsBefore:true, projectDirCreated: async (projectDir, tmpDir) => { const tempDir = await tmpDir.getTempDir() let localPath = path.join(tempDir, "foo") await outputFile(path.join(localPath, "package.json"), `{"name":"foo","version":"9.0.0","main":"index.js","license":"MIT","dependencies":{"ms":"2.0.0"}}`) await outputFile(path.join(localPath, "test.dll"), `test`) await modifyPackageJson(projectDir, data => { data.dependencies = { debug: "3.1.0", "edge-cs": "1.2.1", foo: `file:${localPath}`, } }) }, packed: async context => { await verifySmartUnpack(context.getResources(Platform.WINDOWS)) }, } )() }) ```
1 parent 667ab2f commit f4d40f9

File tree

10 files changed

+360
-83
lines changed

10 files changed

+360
-83
lines changed

.changeset/brave-snakes-unite.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"app-builder-lib": patch
3+
"builder-util": patch
4+
---
5+
6+
fix: smart unpack for local module with dll

packages/app-builder-lib/src/asar/asarUtil.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export class AsarPackager {
141141

142142
for await (const fileSet of fileSets) {
143143
if (this.config.options.smartUnpack !== false) {
144-
detectUnpackedDirs(fileSet, unpackedPaths, this.config.defaultDestination)
144+
detectUnpackedDirs(fileSet, unpackedPaths)
145145
}
146146

147147
// Don't use BluebirdPromise, we need to retain order of execution/iteration through the ordered fileset
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,32 @@
1-
import { log } from "builder-util"
1+
import { log, FilterStats } from "builder-util"
22
import { isBinaryFileSync } from "isbinaryfile"
33
import * as path from "path"
4-
import { NODE_MODULES_PATTERN } from "../fileTransformer"
5-
import { getDestinationPath, ResolvedFileSet } from "../util/appFileCopier"
6-
7-
function addValue(map: Map<string, Array<string>>, key: string, value: string) {
8-
let list = map.get(key)
9-
if (list == null) {
10-
list = [value]
11-
map.set(key, list)
12-
} else {
13-
list.push(value)
14-
}
15-
}
4+
import { ResolvedFileSet } from "../util/appFileCopier"
165

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

2211
/** @internal */
23-
export function detectUnpackedDirs(fileSet: ResolvedFileSet, autoUnpackDirs: Set<string>, defaultDestination: string) {
24-
const dirToCreate = new Map<string, Array<string>>()
12+
export function detectUnpackedDirs(fileSet: ResolvedFileSet, autoUnpackDirs: Set<string>) {
2513
const metadata = fileSet.metadata
2614

27-
function addParents(child: string, root: string) {
28-
child = path.dirname(child)
29-
if (autoUnpackDirs.has(child)) {
30-
return
31-
}
32-
33-
do {
34-
autoUnpackDirs.add(child)
35-
const p = path.dirname(child)
36-
// create parent dir to be able to copy file later without directory existence check
37-
addValue(dirToCreate, p, path.basename(child))
38-
39-
if (child === root || p === root || autoUnpackDirs.has(p)) {
40-
break
41-
}
42-
child = p
43-
} while (true)
44-
45-
autoUnpackDirs.add(root)
46-
}
47-
4815
for (let i = 0, n = fileSet.files.length; i < n; i++) {
4916
const file = fileSet.files[i]
50-
const index = file.lastIndexOf(NODE_MODULES_PATTERN)
51-
if (index < 0) {
17+
const stat: FilterStats = metadata.get(file)!
18+
if (!stat.moduleRootPath || autoUnpackDirs.has(stat.moduleRootPath)) {
5219
continue
5320
}
5421

55-
let nextSlashIndex = file.indexOf(path.sep, index + NODE_MODULES_PATTERN.length + 1)
56-
if (nextSlashIndex < 0) {
57-
continue
58-
}
59-
60-
if (file[index + NODE_MODULES_PATTERN.length] === "@") {
61-
nextSlashIndex = file.indexOf(path.sep, nextSlashIndex + 1)
62-
}
63-
64-
if (!metadata.get(file)!.isFile()) {
65-
continue
66-
}
67-
68-
const packageDir = file.substring(0, nextSlashIndex)
69-
const packageDirPathInArchive = path.relative(defaultDestination, getDestinationPath(packageDir, fileSet))
70-
const pathInArchive = path.relative(defaultDestination, getDestinationPath(file, fileSet))
71-
if (autoUnpackDirs.has(packageDirPathInArchive)) {
72-
// if package dir is unpacked, any file also unpacked
73-
addParents(pathInArchive, packageDirPathInArchive)
22+
if (!stat.isFile()) {
7423
continue
7524
}
7625

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

9342
if (log.isDebugEnabled) {
94-
log.debug({ file: pathInArchive, reason: "contains executable code" }, "not packed into asar archive")
43+
log.debug({ file: stat.moduleFullFilePath, reason: "contains executable code" }, "not packed into asar archive")
9544
}
96-
97-
addParents(pathInArchive, packageDirPathInArchive)
45+
autoUnpackDirs.add(stat.moduleRootPath)
9846
}
9947
}

packages/app-builder-lib/src/util/NodeModuleCopyHelper.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export class NodeModuleCopyHelper extends FileCopyHelper {
4949
super(matcher, matcher.isEmpty() ? null : matcher.createFilter(), packager)
5050
}
5151

52-
async collectNodeModules(moduleInfo: NodeModuleInfo, nodeModuleExcludedExts: Array<string>): Promise<Array<string>> {
52+
async collectNodeModules(moduleInfo: NodeModuleInfo, nodeModuleExcludedExts: Array<string>, destination: string): Promise<Array<string>> {
5353
const filter = this.filter
5454
const metadata = this.metadata
5555

@@ -114,7 +114,9 @@ export class NodeModuleCopyHelper extends FileCopyHelper {
114114
}
115115

116116
return lstat(filePath).then((stat: FilterStats) => {
117-
stat.relativeNodeModulesPath = path.join("node_modules", moduleName, path.relative(depPath, filePath))
117+
stat.moduleName = moduleName
118+
stat.moduleRootPath = destination
119+
stat.moduleFullFilePath = path.join(destination, path.relative(depPath, filePath))
118120
if (filter != null && !filter(filePath, stat)) {
119121
return null
120122
}

packages/app-builder-lib/src/util/appFileCopier.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ export async function computeNodeModuleFileSets(platformPackager: PlatformPackag
192192
const source = dep.dir
193193
const matcher = new FileMatcher(source, destination, mainMatcher.macroExpander, mainMatcher.patterns)
194194
const copier = new NodeModuleCopyHelper(matcher, platformPackager.info)
195-
const files = await copier.collectNodeModules(dep, nodeModuleExcludedExts)
195+
const files = await copier.collectNodeModules(dep, nodeModuleExcludedExts, path.relative(mainMatcher.to, destination))
196196
result[index++] = validateFileSet({ src: source, destination, files, metadata: copier.metadata })
197197

198198
if (dep.conflictDependency) {

packages/app-builder-lib/src/util/filter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function ensureEndSlash(s: string) {
2424
}
2525

2626
function getRelativePath(file: string, srcWithEndSlash: string, stat: FilterStats) {
27-
let relative = stat.relativeNodeModulesPath || file.substring(srcWithEndSlash.length)
27+
let relative = stat.moduleFullFilePath || file.substring(srcWithEndSlash.length)
2828
if (path.sep === "\\") {
2929
if (relative.startsWith("\\")) {
3030
// windows problem: double backslash, the above substring call removes root path with a single slash, so here can me some leftovers

packages/builder-util/src/fs.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,17 @@ export class CopyFileTransformer {
2020

2121
export type FileTransformer = (file: string) => Promise<null | string | Buffer | CopyFileTransformer> | null | string | Buffer | CopyFileTransformer
2222
export interface FilterStats extends Stats {
23-
// relative path of the dependency(node_modules + moduleName + file)
24-
// Mainly used for filter, such as files filtering and asarUnpack filtering
25-
relativeNodeModulesPath?: string
23+
// These module name and paths are mainly used for:
24+
// 1. File filtering
25+
// 2. Asar unpacking rules
26+
// 3. Dependency resolution
27+
28+
// The name of the node module (e.g. 'express')
29+
moduleName?: string
30+
// The root path of the node module (e.g. 'node_modules/express')
31+
moduleRootPath?: string
32+
// The full file path within the node module (e.g. 'node_modules/express/lib/application.js')
33+
moduleFullFilePath?: string
2634
// deal with asar unpack sysmlink
2735
relativeLink?: string
2836
linkRelativeToFile?: string

0 commit comments

Comments
 (0)