electron-builder icon indicating copy to clipboard operation
electron-builder copied to clipboard

Build error "EEXIST: file already exists" caused by `app-builder-bin`

Open zckevin opened this issue 1 year ago • 1 comments

  • Electron-Builder Version: 23.3.3
  • Node Version: v16.16.0
  • Electron Version: 20.0.2
  • Electron Type (current, beta, nightly):
  • Target: dir for debugging

Abstract

Met the same build error at Github actions like #3039 / #3179 , after some console.log debugging and I found that the root cause of it was app-builder.

Project structure

~/PROJECTS/electron/bug-root: tree -L 2                                                                                                                                                                                               .
├── app-root
│   ├── dist
│   ├── index.js
│   ├── node_modules
│   ├── package.json
│   └── package-lock.json
├── node_modules
│   ├── fs-extra
│   ├── graceful-fs
│   ├── jsonfile
│   └── universalify
├── package.json
└── package-lock.json

8 directories, 5 files

package.json in /bug-root:

{
  "name": "bug-root",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "fs-extra": "^10.1.0"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

package.json in /bug-root/app-root/:

{
  "name": "test",
  "version": "0.0.1",
  "dependencies": {
    "fs-extra": "^10.1.0"
  },
  "devDependencies": {
    "electron": "20.0.2"
  },
  "build": {
    "files": [
      "**/*"
    ],
    "linux": {
      "target": "dir",
      "asar": false
    }
  }
}

Both the root and the app folder has fs-extra as dependency.

Run app-builder

Run app-builder node-dep-tree --dir=./bug-root/app-root | jq got:

[
  {
    "dir": "/home/zc/PROJECTS/electron/bug-root/node_modules",
    "deps": [
      {
        "name": "graceful-fs",
        "version": "4.2.10",
        "optional": true
      },
      {
        "name": "universalify",
        "version": "2.0.0"
      }
    ]
  },
  {
    "dir": "/home/zc/PROJECTS/electron/bug-root/app-root/node_modules",
    "deps": [
      {
        "name": "fs-extra",
        "version": "10.1.0"
      },
      {
        "name": "graceful-fs",
        "version": "4.2.10"
      },
      {
        "name": "jsonfile",
        "version": "6.1.0"
      },
      {
        "name": "universalify",
        "version": "2.0.0"
      }
    ]
  }
]

Seems like graceful-fs & universalify would be copied from both /bug-root/node_modules & /bug-root/app-root/node_modules.

After I move /app-root out of /bug-root to /tmp dir, the build process is done suceessfully, and the output of app-builder after then was like:

[
  {
    "dir": "/tmp/app-root/node_modules",
    "deps": [
      {
        "name": "fs-extra",
        "version": "10.1.0"
      },
      {
        "name": "graceful-fs",
        "version": "4.2.10"
      },
      {
        "name": "jsonfile",
        "version": "6.1.0"
      },
      {
        "name": "universalify",
        "version": "2.0.0"
      }
    ]
  }
]

Some printf log proof of work

Add some console.log to app-builder-lib/src/util/appFileCopier.ts

export async function copyAppFiles(fileSet: ResolvedFileSet, packager: Packager, transformer: FileTransformer) {
  const metadata = fileSet.metadata
  // search auto unpacked dir
  const taskManager = new AsyncTaskManager(packager.cancellationToken)
  const createdParentDirs = new Set<string>()

  const fileCopier = new FileCopier(file => {
    // https://github.com/electron-userland/electron-builder/issues/3038
    return !(isLibOrExe(file) || file.endsWith(".node"))
  }, transformer)
  const links: Array<Link> = []
  for (let i = 0, n = fileSet.files.length; i < n; i++) {
    const sourceFile = fileSet.files[i]
    const stat = metadata.get(sourceFile)
    if (stat == null) {
      // dir
      continue
    }

    const destinationFile = getDestinationPath(sourceFile, fileSet)
    console.log("=== copy", sourceFile, destinationFile)       // <------------
    if (stat.isSymbolicLink()) {
      links.push({ file: destinationFile, link: await readlink(sourceFile) })
      continue
    }

    (...)
}

And we could find we did copy graceful-fs twice.

  console.log
    === copy /home/runner/work/electron-asar-differential-builder-debug/electron-asar-differential-builder-debug/node_modules/graceful-fs/LICENSE /home/runner/work/electron-asar-differential-builder-debug/electron-asar-differential-builder-debug/test/dist/unit-nle9lzyn/linux_includesBinaryDir-etwggc0g/linux-unpacked/resources/app/node_modules/graceful-fs/LICENSE

      at Object.copyAppFiles (../node_modules/electron-builder/node_modules/app-builder-lib/src/util/appFileCopier.ts:65:7)

  console.log
    === copy /home/runner/work/electron-asar-differential-builder-debug/electron-asar-differential-builder-debug/test/dist/unit-nle9lzyn/app-root-0.0.1/node_modules/graceful-fs/LICENSE /home/runner/work/electron-asar-differential-builder-debug/electron-asar-differential-builder-debug/test/dist/unit-nle9lzyn/linux_includesBinaryDir-etwggc0g/linux-unpacked/resources/app/node_modules/graceful-fs/LICENSE

      at Object.copyAppFiles (../node_modules/electron-builder/node_modules/app-builder-lib/src/util/appFileCopier.ts:65:7)

The error log:

    Error #1 --------------------------------------------------------------------------------
    Error: EEXIST: file already exists, link '/home/runner/work/electron-asar-differential-builder-debug/electron-asar-differential-builder-debug/test/dist/unit-nle9lzyn/app-root-0.0.1/node_modules/graceful-fs/LICENSE' -> '/home/runner/work/electron-asar-differential-builder-debug/electron-asar-differential-builder-debug/test/dist/unit-nle9lzyn/linux_includesBinaryDir-etwggc0g/linux-unpacked/resources/app/node_modules/graceful-fs/LICENSE'

    Error #2 --------------------------------------------------------------------------------
    Error: EEXIST: file already exists, link '/home/runner/work/electron-asar-differential-builder-debug/electron-asar-differential-builder-debug/test/dist/unit-nle9lzyn/app-root-0.0.1/node_modules/graceful-fs/clone.js' -> '/home/runner/work/electron-asar-differential-builder-debug/electron-asar-differential-builder-debug/test/dist/unit-nle9lzyn/linux_includesBinaryDir-etwggc0g/linux-unpacked/resources/app/node_modules/graceful-fs/clone.js'

    (...)

Final

As folks in #3179 point out that we could mitigate this with USE_HARD_LINKS=false, but to fix this I think we could add a deduplicate step before file copy.

zckevin avatar Aug 26 '22 07:08 zckevin

Happy to review a PR for your suggestion! I actually am very unfamiliar with the file-copying code, I just haven't been able to wrap my head around it :/

mmaietta avatar Aug 31 '22 16:08 mmaietta