[Electron] Electron 운영체제에 따른 디버깅과 app.getPath()

minidoo·2020년 8월 31일
7

Electron

목록 보기
4/4

Electron 프로젝트를 진행하면서 가장 헤맸던 부분은 디버깅이었다.
s3에서 다운 받은 파일을 로컬에 저장하는 과정에서 개발/운영 모드, 운영체제에 따라 코드를 다르게 작성해야하는데 Electron에 대해 자세히 몰랐던터라 미흡한 점이 많았다 :(

디버깅(Debugging)

Electron은 운영체제에 따라 다른 디버깅 명령어를 입력해야 한다. 실행 파일 자체가 다른 형태로 나오기 때문에 반드시 지켜야할 부분이다. (Mac과 Window 위주로 설명)

"mac" : "npm run build --mac"
"win64" : "npm run build --win --x64"
"linux64" : "npm run build --linux --x64"

디버깅을 완료하면 (좌)Mac은 'My Nextron App'이라는 이름으로 프로그램 자체가 생성된다. 앱의 이름은 electorn-builder.yml 파일에서 productName의 값을 바꾸면 변경된다.

(우)Window는 /dist 폴더 안에 exe 확장자의 설치 파일이 생성된다. 예를 들어, 윈도우에서 카카오톡을 다운 받으면 실행 전 이것저것 설치해야 하는 것과 같은 맥락으로 볼 수 있다.

Mac에서도 앱 설치 파일의 상세 내용을 확인 할 수 있다. 앱 파일명을 우클릭한 후 패키지 내용 보기를 선택하면 /Contents/MacOS에서 설치 파일을 확인할 수 있다.

다만, 오류를 확인하는데 있어 해당 실행 파일은 그닥 친절하지 못하다. background에서 작동하기 때문에 자세한 디버깅 오류를 알 수 없었다! 따라서 디버깅 오류는 forground(ex. visual studio code terminal)에서 확인하는 것이 편하다 :)


개발 모드 vs 운영 모드

__dirname과 process.execPath를 이용하여 로컬에 파일 저장하기 (오류)

/main/background.js

import electron, { app } from 'electron';
const isProd = process.env.NODE_ENV == 'production'

if (isProd) {
  serve({ directory: 'app' });
} else {
  app.setPath('userData', `${app.getPath('userData')} (development)`);
}

...

main 프로세스의 background.js 코드를 자세히 살펴보면, process.env.NODE_ENV 로 개발과 운영 모드로 분리하여 코드가 작성된 것을 볼 수 있다. 로컬에 파일을 저장하기 위해 기본 셋팅된 코드로 힌트를 얻어 작성해보았다.

/main/background.js

...

let directoryPath

if(isProd) {
  directoryPath = path.join(process.execPath, '/.uploadList/');
} else {
  directoryPath = path.join(__dirname, '/.uploadList/');
}

...

(async () => {
  await app.whenReady();
	
  // 폴더 생성
  isProd ? fs.mkdirSync(process.execPath + '/.uploadList') 
      :	fs.mkdirSync(__dirname + '/.uploadList');
})();

...
  • __dirname : 현재 실행 중인 폴더 경로 (개발)
  • process.execPath : node 실행 파일의 절대 경로 (운영)

이전 게시글에서 환경변수 __dirnameprocess.execPath에 대해 정리해보았다.

Mac을 기준으로 __dirname은 '/Users/이름/폴더명/프로젝트명/app' 을, process.execPath는 '/Users/이름/폴더명/프로젝트명/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron'을 가리킨다.


위 코드의 에러는 무엇일까?

개발 모드에서 앱을 실행시키면 에러가 발생하지 않는다. __dirname의 경로가 폴더 형태이기 때문이다. 다운 받은 파일은 '/app' 폴더에 저장된다. 하지만, 운영 모드를 위한 디버깅 시 에러가 발생한다. process.execPath 경로는 폴더 형태가 아니기 때문이다! 이 경로의 도착점은 실행 파일 자체이며 이곳에 파일이 저장되는 것은 불가능하다.


여기서 문제를 두 가지 정도로 요약해볼 수 있겠다.

1. 어디에 파일을 저장하는 것이 모든 실행 환경에서 작동될 수 있을까?
2. 다른 운영체제는 어떤 식으로 동작할까?

이 모든 것을 해결해주는 Electron의 메소드가 바로 getPath이다.


app.getPath(name)

Electron의 app 객체는 다양한 메소드를 제공한다.
예를 들어, 이전 게시글에서 살펴본 app.quit()는 앱을 종료시켜준다. 다만, 몇몇 메소드는 특정 운영체제에서만 사용할 수 있기 때문에 공식 홈페이지로 확인해본 후 사용하는 것을 추천한다.

app.on('window-all-closed', () => {
  app.quit();	// 앱 종료
});

app.getPath(name)은 무엇은 의미할까?

app.getPath(name)는 운영체제에 따라 다른 경로를 제공하는 메소드이다. 인자값 name에는 home, appData, userData, exe 등을 넣으면 된다.

  • home : User의 home 디렉토리 ( = process.env.HOME )
  • exe : 현재 실행파일의 경로 ( = process.execPath )
  • appData : 사용자별 앱 데이터 디렉토리
    • Linux : $XDG_CONFIG_HOME 또는 ~/.config
    • macOS : ~/Library/Application Support ( * 숨긴 폴더 )
    • Window: %APPDATA%
  • userData : appData 뒤에 앱의 이름이 붙은 형태, 앱의 설정 파일을 저장할 디렉토리

getPath에 대해 알았다면, main 프로세스에 기본으로 셋팅된 코드를 이해할 수 있었을텐데...!!
/main/background.js

 if (isProd) {
  serve({ directory: 'app' });
} else {
  app.setPath('userData', `${app.getPath('userData')} (development)`);
}

${app.getPath('userData')} (development) 는 '~/Library/Application Support' 폴더 안에 '앱 이름 (development)'를 나타내는 코드였던 것이다! 게다가 운영체제 별로 경로가 다르게 설정된다니 Electron은 정말 놀라움의 연속이다.


getPath 사용하여 로컬에 파일 저장하기

/main/background.js

import electron, { app } from 'electron';

const isProd = process.env.NODE_ENV === 'production';
const fs = require('fs');

if (isProd) {
  serve({ directory: 'app' });
} else {
  app.setPath('userData', `${app.getPath('userData')} (development)`);
}

let directoryPath;

(async () => {
    ...
    
    directoryPath = `${app.getPath('userData')}/.uploadList/`;
    fs.mkdirSync(directoryPath);
})();

getPath를 알고 나니 코드 읽기도 매우 간단해졌다.

'~/Library/Application Support/앱 이름/uploadList' 안에 파일이 저장되었다. 개발과 운영 모드에 따라 폴더 명을 다르게 설정해주었기 때문에 개발과 운영 사이의 캐시가 중복될 일도 없다. getPath가 이만큼 편리한 것이었다!

마무리하며

기본적인 개념 없이 Electron 프로젝트를 시작했기 때문에 알고보면 간단했지만, 어려웠던 것들이 많았다.
그러나 배워갈수록 굉장히 매력적인 프레임워크임이 느껴진다. Electron으로 프로젝트를 다시 한 번 진행하게 된다면 그땐 정말 제대로 알고 사용해보고 싶다 :)

공식 홈페이지 https://www.electronjs.org/docs/api/app#appgetpathname

4개의 댓글

comment-user-thumbnail
2021년 6월 24일

와 이번 Nextron에 대해서 정리해주신 것을 보고 정말 많이 배울 수 있었습니다.
특히~ Next.js의 CSR, SSR 설명해주신 내용도 그렇구,
IPC 통신에 대해서 정말 이해할 수 밖에 없게 말씀해주셔서 너무너무 많이 도움이 됐습니다!ㅎㅎ
감사해용~~

1개의 답글
comment-user-thumbnail
2024년 3월 24일

안녕하세요, vue project를 electron 을 통해 데스크탑앱으로 바꿀려고 노력중인 학생인데요, 실례가 안된다면 질문 할 수 있을까요??
제 파일 구조가 다음과 같은데,개발 모드에서는 public의 model.json들을 접근할 수 있는데, build된 모드에서는 public의 model.json들을 접근이 제대로 안되더라구요. 혹시 어떻게 해야 접근 가능한지 알려주실 수 있을까요??? 어케 해야 될지 모르겠습니다... 관련된 책도 너무 오래된 것밖에 없는 것 같구요...

파일 구조 :
Lopporter
.
.
.
ㄴpublic
ㄴㄴcnn
ㄴㄴㄴmodel.json
ㄴㄴyolo1
ㄴㄴㄴmodel.json
ㄴㄴyolo2
ㄴㄴㄴmodel.json

ㄴsrc
ㄴㄴcomponent
ㄴㄴㄴBossPredictor.vue

ㄴApp.vue

ㄴbackground.js

ㄴmain.js
.
.
.
코드 :
async loadModels() {
const isDevelopment = process.env.NODE_ENV !== "production";

  if (isDevelopment) {
    this.basePath = `http://localhost:8080/`; // Adjust if your dev server runs on a different port
  } else {
    this.basePath = `file://${modelsPath}/`;
  }

  try {
    // Assuming models are stored in a folder named 'models' within your resources in production
    yolo1Model = await tf.loadGraphModel(
      `${this.basePath}models/yolo14/model.json`
    );
    console.log("YOLO model 1 loaded successfully.");

    yolo2Model = await tf.loadGraphModel(
      `${this.basePath}models/yolo23/model.json`
    );
    console.log("YOLO model 2 loaded successfully.");

    resnetModel = await tf.loadLayersModel(
      `${this.basePath}models/cnn/my-model.json`
    );
    console.log("ResNet model loaded successfully.");

    this.modelReady = true;
  } catch (error) {
    console.error("Error loading models:", error);
  }
},
1개의 답글