refac: python tar

This commit is contained in:
Timothy Jaeryang Baek 2025-01-11 12:11:47 -08:00
parent be467b390c
commit 4e99cf651e
6 changed files with 1232 additions and 20 deletions

4
.gitignore vendored
View File

@ -91,5 +91,5 @@ typings/
# Electron-Forge
out/
resources/python.tar.gz
resources/python
resources/python.tar.gz

View File

@ -9,7 +9,9 @@
"package": "electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish",
"lint": "eslint --ext .ts,.tsx ."
"lint": "eslint --ext .ts,.tsx .",
"create:conda-lock": "cd resources && rimraf conda-lock.yml && conda-lock -f environment.yml && cd -",
"create:python-tar": "rimraf ./resources/python.tar.gz && conda-lock install --prefix ./resources/python ./resources/conda-lock.yml && conda pack -p ./resources/python -o ./resources/python.tar.gz"
},
"devDependencies": {
"@electron-forge/cli": "^7.6.0",

1125
resources/conda-lock.yml Normal file

File diff suppressed because it is too large Load Diff

12
resources/environment.yml Normal file
View File

@ -0,0 +1,12 @@
# environment.yml
channels:
- conda-forge
dependencies:
- python=3.11
platforms:
- linux-64
- linux-aarch64 # aka arm64, use for Docker on Apple Silicon
- osx-64
- osx-arm64 # For Apple Silicon, e.g. M1/M2
- win-64
# TODO: Add win-arm64 when available

View File

@ -45,6 +45,9 @@ if (!gotTheLock) {
const onReady = () => {
console.log(process.resourcesPath);
console.log(app.getName());
console.log(app.getPath("userData"));
console.log(app.getPath("appData"));
mainWindow = new BrowserWindow({
width: 800,
height: 600,

View File

@ -20,7 +20,7 @@ import { app } from "electron";
//
////////////////////////////////////////////////
export function getAppDir(): string {
export function getAppPath(): string {
let appDir = app.getAppPath();
if (!app.isPackaged) {
appDir = path.dirname(appDir);
@ -28,11 +28,11 @@ export function getAppDir(): string {
return appDir;
}
export function getUserHomeDir(): string {
export function getUserHomePath(): string {
return app.getPath("home");
}
export function getUserDataDir(): string {
export function getUserDataPath(): string {
const userDataDir = app.getPath("userData");
if (!fs.existsSync(userDataDir)) {
@ -52,16 +52,13 @@ export function getUserDataDir(): string {
//
////////////////////////////////////////////////
export function getPackagedPythonTarPath(): string {
const appDir = getAppDir();
return path.join(appDir, "resources", "python.tar.gz");
export function getBundledPythonTarPath(): string {
const appPath = getAppPath();
return path.join(appPath, "resources", "python.tar.gz");
}
export function getBundledPythonEnvDir(): string {
const installDir =
process.platform === "darwin"
? path.normalize(path.join(app.getPath("home"), "Library", app.getName()))
: app.getPath("userData");
export function getBundledPythonInstallPath(): string {
const installDir = path.join(app.getPath("userData"), "python");
if (!fs.existsSync(installDir)) {
try {
@ -73,12 +70,6 @@ export function getBundledPythonEnvDir(): string {
return installDir;
}
export function getBundledPythonEnvPath(): string {
const userDataDir = getBundledPythonEnvDir();
return path.join(userDataDir, "python");
}
export function isCondaEnv(envPath: string): boolean {
return fs.existsSync(path.join(envPath, "conda-meta"));
}
@ -92,3 +83,82 @@ export function getPythonPath(envPath: string, isConda?: boolean) {
return path.join(envPath, "bin", "python");
}
}
export function getBundledPythonPath() {
return getPythonPath(getBundledPythonInstallPath());
}
export function isBundledPythonInstalled() {
return fs.existsSync(getBundledPythonPath());
}
////////////////////////////////////////////////
//
// Fixes code-signing issues in macOS by applying ad-hoc signatures to extracted environment files.
//
// Unpacking a Conda environment on macOS may break the signatures of binaries, causing macOS
// Gatekeeper to block them. This script assigns an ad-hoc signature (`-s -`), making the binaries
// executable while bypassing macOS's strict validation without requiring trusted certificates.
//
// It reads an architecture-specific file (`sign-osx-arm64.txt` or `sign-osx-64.txt`), which lists
// files requiring re-signing, and generates a `codesign` command to fix them all within the `envPath`.
//
////////////////////////////////////////////////
export function createAdHocSignCommand(envPath: string): string {
const appPath = getAppPath();
const signListFile = path.join(
appPath,
"resources",
`sign-osx-${process.arch === "arm64" ? "arm64" : "64"}.txt`
);
const fileContents = fs.readFileSync(signListFile, "utf-8");
const signList: string[] = [];
fileContents.split(/\r?\n/).forEach((line) => {
if (line) {
signList.push(`"${line}"`);
}
});
// sign all binaries with ad-hoc signature
return `cd ${envPath} && codesign -s - -o 0x2 -f ${signList.join(
" "
)} && cd -`;
}
export async function installBundledPython(installPath?: string) {
const platform = process.platform;
const isWin = platform === "win32";
installPath = installPath || getBundledPythonInstallPath();
const pythonTarPath = getBundledPythonTarPath();
if (!fs.existsSync(pythonTarPath)) {
log.error("Python tarball not found");
return;
}
try {
fs.mkdirSync(installPath, { recursive: true });
await tar.x({
cwd: installPath,
file: pythonTarPath,
});
} catch (error) {
log.error(error);
}
let unpackCommand = isWin
? `${installPath}\\Scripts\\activate.bat && conda-unpack`
: `source "${installPath}/bin/activate" && conda-unpack`;
// only unsign when installing from bundled installer
if (platform === "darwin") {
unpackCommand = `${createAdHocSignCommand(installPath)}\n${unpackCommand}`;
}
const commandProcess = exec(unpackCommand, {
shell: isWin ? "cmd.exe" : "/bin/bash",
});
}