[react-query] 리렌더링 시 useMutation 의 동작과 주의사항

useMutation 은 useEffect, useCallback 같은 hook 과는 다르게 의존성을 설정할 수 없다. 그럼, 리렌더링 시에 useMutation에서 반환되는 mutate() 는 항상 새로 만들어 지는 걸까?

useMutation 에 파라메터로 넘기는 mutaionFn, onMutate, onSettled, onSuccess 등이 리렌더링 시 변경될 경우, mutate() 를 호출 하면 각 상황에서 변경된 함수가 호출되는 것으로 봐서 그럴 수도 있어 보인다.

그렇다면 mutate() 를 호출 해서 mutationFn 이 이미 실행 중인 상황에서 리렌더링이 일어나 onSuccess 등이 변경 되고, 그 후 mutationFn 의 실행이 완료된다면 어떻게 될까? mutationFn 이 성공적으로 수행된 후 호출 되는 onSuccess 는 앞서 mutate() 가 호출 되었을 당시의 onSuccess 함수가 아니라 리렌더링 중 변경된 onSuccess 함수가 호출된다.

실제 react-query 가 어떻게 구현되어 있는지 살펴보자.

  const [observer] = React.useState(
    () =>
      new MutationObserver<TData, TError, TVariables, TContext>(
        queryClient,
        options,
      ),
  )

useMutation 의 구현을 보면 observer 라는 state 를 만들어 쓰는 것을 볼 수 있다.

  React.useEffect(() => {
    observer.setOptions(options)
  }, [observer, options])

useMutation 에 파라메터로 넘어가는 옵션이 변경될 경우, 이 observer state 의 setOptions() 를 호출 하여 변경해 주게되며,

  const mutate = React.useCallback<
    UseMutateFunction<TData, TError, TVariables, TContext>
  >(
    (variables, mutateOptions) => {
      observer.mutate(variables, mutateOptions).catch(noop)
    },
    [observer],
  )

우리가 useMutation() 의 결과로 반환받는 mutate 는 useCallback() 을 사용하여 만드는데, 의존성 목록에는 observer 만 들어가 있어 mutate 는 새로 만들어지지 않는다.

좀 더 안 쪽을 살펴 보자. observer 의 mutate() 를 호출 했을때, 즉, 우리가 useMutation()에서 반환 받은 mutate() 함수를 호출 했을 때 무슨일이 일어나는걸까?

  mutate(
    variables?: TVariables,
    options?: MutateOptions<TData, TError, TVariables, TContext>,
  ): Promise<TData> {
    this.mutateOptions = options

    if (this.currentMutation) {
      this.currentMutation.removeObserver(this)
    }

    this.currentMutation = this.client.getMutationCache().build(this.client, {
      ...this.options,
      variables:
        typeof variables !== 'undefined' ? variables : this.options.variables,
    })

    this.currentMutation.addObserver(this)

    return this.currentMutation.execute()
  }

mutate() 가 호출 되는 시점에 현재의 옵션을 넘겨서 Mutation 객체를 새로 만들고, 마지막에 Mutation.execute() 함수를 호출해 준다.

async execute(): Promise<TData> {
    const executeMutation = () => {
      this.retryer = createRetryer({
        fn: () => {
          if (!this.options.mutationFn) {
            return Promise.reject('No mutationFn found')
          }
          return this.options.mutationFn(this.state.variables!)
        },
...
      })

      return this.retryer.promise
    }

...
      const data = await executeMutation()

      // Notify cache callback
      await this.mutationCache.config.onSuccess?.(
        data,
        this.state.variables,
        this.state.context,
        this as Mutation<unknown, unknown, unknown, unknown>,
      )
...
  }

Mutation.execute() 는 생성 시 넘겨 받았던 options의 mutationFn 을 호출 하고, 그 결과에 따라 onSuccess 등을 호출 해 준다.

여기까지 봤을때는, mutate() 호출 시 객체를 생성하고, 필요한 정보는 해당 객체가 다 가지고 있는 형태이므로 이후, useMutation() 이 새로 호출되고 옵션이 바뀌더라도, 현재 실행중인 mutate 에는 영향을 미치지 않을 것으로 보인다.

그런데 아직 보지 못한 곳이 있다. observer의 setOptons().

  setOptions(
    options?: MutationObserverOptions<TData, TError, TVariables, TContext>,
  ) {
    const prevOptions = this.options
    this.options = this.client.defaultMutationOptions(options)
    if (!shallowEqualObjects(prevOptions, this.options)) {
      this.client.getMutationCache().notify({
        type: 'observerOptionsUpdated',
        mutation: this.currentMutation,
        observer: this,
      })
    }
    this.currentMutation?.setOptions(this.options)
  }

뭔가 이유가 있겠지만, 개인적으로는 좀 의아한데… 마지막에 현재 mutation, 즉, 마지막으로 mutate() 호출될 당시의 mutation 객체의 옵션을 같이 바꿔준다.

이로인해, mutaionFn 이 실행 중 useMutation의 옵션이 바뀌게 되면, mutationFn 이 끝난 후 처리는 바뀐 옵션대로 하게되는 것이다.

여기서 useMutation() 사용할때 한 가지 주의 사항이 생긴다. mutate()를 호출한 후 useMutation()의 옵션이 변하는 경우에 대해 고려하라는 것.

의도한게 아니라면, mutate() 호출이 완료되기 전에 useMutation() 의 옵션이 변경된는 상황은 막아야 한다. 보통 react-query 를 사용하는 목적을 생각해보면, mutationFn 을 API 호출 작업일 것이고, 해당 작업은 서버나 네트워크 상황 등에 따라 매우 느리게 끝날 가능성이 있다. 그리고, 그 사이 useMutation()의 옵션에 영향을 주는 state 가 변경되어 리렌더링이 일어나면, mutationFn 이 완료되었을 때는 의도치 않은 작업이 수행될 수 있다.

React native + axios 로 json + image multipart 데이터 전송하기

React native 에서 사용되는 FormData 는 form-data 모듈과는 뭔가 다른지, multipart로 전송할 경우에 각 파트의 content-type 을 지정할 수 없어서 애를 먹었다. (디폴트 application/octect-stream 으로 설정됨)

이미지는 uri 프로퍼티에 파일경로를 type 프로퍼티에 타입을 지정해서 객체로 넘기면 된다는 거까지는 쉽게 알아냈는데, JSON은 도통 방법을 찾을 수가 없다.

거의 포기할려던 찰나… string 프로퍼티에 JSON을 문자열로 변환하여 넘기면 된다는 내용을 발견! 해결되었다.

import axios from "axios";

exprot const submitMultipart = (body: any) => {
  const formData = new FormData();

  formData.append("jsonData", {"string": JSON.stringfy(body.jsonData, type: "application/json"});
  formData.appand("image", {uri: body.imageUri, type: "image/jpeg"});

  const instance = axios.create();
  return await instance({
    url: "/post-data",
    method: "post",
    data: formData,
    headers: {
      "content-type: "multipart/form-data"
    }
  });
}

참고 : https://stackoverflow.com/questions/32441963/how-to-use-formdata-in-react-native

[GitHub Actions] 컨텐츠 NuGet 패키지 생성 및 배포하기

C# 프로젝트 진행 중 다른 웹프로젝트의 결과물을 포함해야 되는 상황에 직면했다. 여러가지 방향을 고민 했지만, 최종적으로 결정된건, 해당 웹프로젝트의 결과물을 NuGet 패키지로 만들어서 GitHub의 NuGet Registry 로 배포하고, 이 패키지를 C# 프로젝트에 포함하는 방식.

우선 웹프로젝트의 GitHub 레파지토리에 .github/workflows 폴더에 actions 스크립트를 추가한다.

앞부분에는 프로젝트에 따라 결과물을 생성할 수 있는 빌드 스크립트를 적당히 추가해주고, 이어서 다음과 같이 NuGet.exe 를 사용한 패키지 생성 및 배포 스크립트를 추가한다.

GitHub 에서 해당 레파지토리의 NuGet Registry에 배포를 할 경우, ApiKey 로 secrets.GITHUB_TOKEN을 사용하면 된다.

...

jobs:
  build:
    steps:

      ...

      # NuGet.exe 설치
      - name : Setup NuGet
        uses: NuGet/setup-nuget@1.0.7
        with:
          nuget-version: latest
 
      # NuGet 패키지 생성
      - name: Create NuGet package
        run: nuget pack

      - name: Upload NuGet package
        run: nuget push *.nupkg -configfile NuGet.config -ApiKey ${{ secrets.GITHUB_TOKEN }} -Source "github" -SkipDuplicate

-SkipDuplicate 옵션을 사용하면, 해당 패키지의 버전이 이미 Registry에 존재하더라도 Actions 스크립트의 실행이 실패하지 않도록 하여 주므로, 평소에는 패키지 배포 때문에 Actions 실행이 실패하지 않으면서도 실제 배포가 필요한 경우에만 패키지 버전을 변경하는 방식으로 배포여부를 제어할 수 있다.

NuGet 패키지를 생성하기 위해서는 루트 폴더에 다음과 같이 .nuspec 파일을 만들어 주어야 한다.

<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
	<metadata>
		<id>[Company].[Project].[PackageName]</id>
		<version>1.0.0</version>
		<authors>[Company]</authors>
		<owners>[Company Name]</owners>
		<projectUrl>https://www.company.com</projectUrl>
		<repository type="git" url="https://github.com/[owner]/[repository].git" />
		<description>My Company NuGet Package</description>
		<requireLicenseAcceptance>false</requireLicenseAcceptance>
		<copyright>Copyright (c)2022 My Company corp.</copyright>
		<dependencies></dependencies>
		<contentFiles>
			<files include="any\any\**" buildAction="EmbeddedResource" copyToOutput="true" flatten="false"/>		
		</contentFiles>
	</metadata>

	<files>
		<file src="build\**" target="contentFiles\any\any\WebApplication"/>
	</files>
</package>

<id> 는 다른 패키지와 겹치지 않도록 적당한 규칙으로 부여해주면 되고, <repository> 에서 [owner] 에는 레파지토리 소유자 이름, [Repository]에는 해당 레파지토리 이름을 적어 줘야한다. 즉, 여기서는 Package를 업로드할 github 레파지토리의 URL을 적어주면된다. 나머지 항목들은 상황에 맞처 적당히 적어주면 된다.

<files>에서 패키지에 포함할 파일들을 지정한다. 여기서는 build 폴더 하위의 모든 항목을 패키지 내의 contentFiles\any\any\WebApplication 폴더 하위에 넣도록 지정했다. contentFiles 폴더는 패키지내에 컨텐츠 파일들을 저장하는 경로이고, any/any 는 모든 플랫폼과 버전에 해당하는 파일의 경로다.

여기서 또 중요한 건 <contentFiles> 항목이다. 여기서는 패키지 내의 컨텐츠 파일에 대한 설정을 하는데, 기본적으로 컨텐츠 파일의 경로는 패키지 내의 contentsFiles 폴더 하위다. 즉, 위의 <files> 에서 지정한 파일들이 대상이 되는 것이다.

여기서는 any\any 폴더 하위의 모든 파일과 폴더에 대해 빌드 결과물 폴더로 복사하고(copyToOutput=”true”), 복사할때 폴더 구조를 그대로 유지하도록 (flatten=”false”) 설정했다.

이제 패키지를 업로드할 NuGet Registry 설정을 추가하기 위해 프로젝트 폴더 루트에 NuGet.config 파일을 생성하자.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <packageSources>
        <clear />
        <add key="github" value="https://nuget.pkg.github.com/[owner]/index.json" />
    </packageSources>
</configuration>

여기서 <packageSources> 의 <add> 에서 정의한 key의 값이 앞서 Actions 스크립트에서 nuget push 시 지정한 -Source 옵션의 값이다. .nuspec 파일과는 다르게 여기서는 URL은 nuget.pkg.github.com 으로 하여야 하며, 패키지를 배포하는 registry 의 owner 를 [owner] 에 적어주면되다.

여기서 주의 할 것은, 패키지를 생성하는 레파지토리와 NuGet Registry의 owner 가 일치할 경우에만, nuget push 시에 secrets.GITHUB_TOKEN 을 ApiKey 로 사용할 수 있다는 것이다. 각각의 owner 가 다를 경우에는 권한이 있는 계정의 Peronal Access Token 을 생성하고, 프로젝트의 레파지토리 설정에서 secret으로 추가하는 작업을 해주어야 한다.(이경우는 -ApiKey 옵션이 아니라 -username 과 -password 옵션을 사용하여야 한다)

세 파일을 레파지토리에 추가하고 GitHub 레파지토리로 push 해 주면 해당 프로젝트의 결과물을 NuGet 패키지로 배포할 수 있는 준비가 끝난다.

언리얼 엔진 5 – 프로젝트 패키징 안되는 문제 (SDK 설치하기)

언리얼로 만든 결과물을 최종적으로 윈도우용으로 배포할 수 있게 패키징을 하려면, 상단 툴바에서 [플랫폼] -> [Windows] -> [프로젝트 패키지] 를 클릭하면 된다.

으잉? 근데 안된다….

아까 프로젝트 패키지 를 실행할때도 하단에 SDK 관련해서 에러 메시지 같은게 있더라니…

그래서 [계속] 을 클릭해줘면… 역시나 에러 메시지만 뜰 뿐….

그래서 계속 시도해 봤지만 여전히 에러메시지만 볼 수 있을 뿐이었다. 그래서, 이 놈의 SDK는 도대체 왜 설치가 되지 않았는가.. 하며 이거저거 뒤지다 보니…

이런… 필요한건 언리얼 SDK가 Windows SDK 였다. 으… 이런건 설치할 때 알아서 깔아 주거나 안내를 해줘야지…

… 내가 메시지를 제대로 안 봤나;;;;;

아무튼… Windows SDK가 필요하다니 마이크로소프트 홈페이지로 가서 설치파일을 구하자

  • Windows SDK :

https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/

위 링크로 들어가서 스크롤을 좀 내리면 [Download the installer] 라는 버튼이 있다.

요걸 클릭해서 설치 파일을 다운 받아 실행 한 후, Next 와 Download 를 클릭해면 끝.

그리고 왜 때문인지 .NET 도 설치가 필요하다고 한다.

  • .NET Core 3.1 :

https://dotnet.microsoft.com/en-us/download/dotnet/3.1

음.. 이걸 설치 안 하고는 테스트를 안 해봤는데, 설치가 불필요할 수도 있으니, Windows SDK를 설치해도 안 되는 경우에 시도해 보도록 하자.

설치된 윈도우 버전에 따라 64 bit 버전은 [x64], 32 bit 버전은 [x86] 을 클릭하여 설치파일을 다운 받은 후 설치 해주면 된다.

그리고, 다시 언리얼 엔진 으로 돌아와 프로젝트 패키지가 있는 메뉴를 열어보면…

아래 쪽 SDK 관련된 부분이 바뀐걸 볼 수 있다. 이제 프로젝트 패키지를 실행해보자.

아래쪽 구석에 패키징이 진행 중이라는 메시지가 뜨고, 아~주 조금만 (물론 패키징할 크기와 PC 성능에 따라 많이 일수도) 기다리면…

패키징이 완료 됐다는 반가운 메시지를 맞이 할 수 있다.

혹시, 패키징 진행 중에 아래와 같은 경고 창이 뜨면 [액세스 허용]을 클릭해 주면 된다.

블루베리 담금주 만들기

블루베리 담금주를 만드려고 싼마이 냉동 블루베리를 사 놓고 차일 피일 미룬지 어언 한달….

진작에 담궜으면 벌써 먹고 있을수도 있는데…(한달 정도면 먹을 순 있지만 가능하면 세 달 정도는 숙성시켜 먹는게 좋다고 한다)

더 이상 미룰 수 없어 드디어 블루베리 담금주 만들기를 결행한닷!

재료 : 냉동 블루베리 1kg, 설탕 200g, 담금소주 30% 1.8L, 3.6L 담금주병

과일 담금주의 경우에는 숙성하는 동안 과일에서 수분이 빠져나와 알콜도수가 낮아지기 때문에 너무 낮은 도수의 담금소주를 사용하면 상해버릴수도 있다. 30% 이상되는 담금소주를 사용하는게 좋은데, 과일의 수분함량에 따라 다르지만. 30% 정도를 사용하면 20 중반 정도의 술을 만들 수 있다.

우선, 술을 담글 병을 열탕 소독해주기 위해서 냄비에 물을 넣고 병을 거꾸로 뒤집어 넣은 후 물을 끓여준다. 뜨거울때 병을 넣을 경우 온도차로 병이 깨질 수도 있으므로 물을 끓이기 전 부터 넣어주는 것이 좋다.

물이 끓기 시작하면 5분 정도 증기를 한껏 받아 소독이 될 수 있도록 놔둔다.

병을 꺼내 물기를 한번 털고나서, 병이 식을 동안 잠시 둔다. 병의 열기로 인해 내부의 수분 대부분을 빠르게 증발해버린다.

그래도 남아있는 물기는 깨끗이 닦아주도록 하자.

바닥에 블루베리를 좀 깔아주고,

그리고, 그 위에 설탕을 뿌려준다

같은 방법으로 블루베리와 설탕을 번갈아 넣어준다.

설탕을 넣어서 발효를 시킨다는 썰도 있는데…그닥 말은 안되는거 같고… 술에 단맛을 첨가해준다고 보면 된다. 설탕은 본인 기호에 맞게 조절하자. (너무 많이 넣으면 취하는 줄도 모르고 계속 마시다가 한방에 훅 가는 수도 있다)

블루베리와 설탕을 다 넣으면 뚜껑을 닫고 하루 정도 방치해둔다.

어느정도 지나면 블루베리에서 수분이 빠져나와 설탕이 다 녹는다.

이젠 담금소주의 시간.

소주를 콸콸콸 부어주면

영롱한 빛깔의 블루베리주 완성!

… 이 아니라 이제부터 인고의 시간을 보내야 한다ㅠ

블루베리주는 숙성을 위해 3개월 뒤를 기약하며 서늘한 곳으로….

언리얼 엔진 5 Quixel Bridge – 로그인실패 및 “Failed to restart background service” 에러 수정하기

요즘 언리얼 엔진을 배운답시고 깔짝대고 있는데…

언리얼 엔진을 사용하는데 필수라는 Quixel Bridge가 제대로 작동이 안된다.

일단 로그인을 시도하면 자꾸 먹통이 되어버리고, 또, 왼쪽 구석에 “Failed to restart background service” 메시지가 자꾸 뜨는데, 요건 Restart now를 몇 번 누르다 보면 괜찮은거 같기도 하고…

백그라운드 서비스 머시기는 뭔지 모르겠고, 아무튼 일단 로그인이 되어야 뭐라도 될텐데 싶어서 구글의 힘을 좀 빌렸 봤으나 뾰족한 해결잭은 보이지 않았다.

그럼 백그라운드 머시기 에러라도 수정하려고 뒤지다보니

https://help.quixel.com/hc/en-us/community/posts/4409168256401–Failed-to-restart-background-service-after-UE5-Quixel-Bridge-update

개발자가 그 문제는 수정을 했으니 플러그인을 업데이트 하란다. 이미 난 최신버전인데…. 음.. 이것도 아닌가… 하면서 아래 댓글들을 읽다보니, 하라는대로 해서 해결이 됐데. 다시 개발자가 쓴 글을 보니 마지막 부분을 놓쳤더라고.

Quixel Bride를 실행하면 오른쪽 위에 사람 모양의 아이콘이 보이는데 요놈을 클릭해서 나오는 메뉴에서 Preferences를 클릭하면

설정화면 제일 위에 Library Path 가 보인다. 이게 설정이 잘못된게 원인인데… 이미 설정을 바꿔버려서 모르겠지만 아마 권한이 없는 경로가 설정되어 있었던듯.

적당한 경로에 폴더를 하나 만들어 주고,

설정을 해당 폴더로 바꿔주면..

짜잔~

더 이상 에러가 나지 않는다.

더군다나 로그인도 정상적으로 되네?

그냥 설치만하고 설정에 손도 안됐으니..

기본 설정이 잘못되어 있었던데다,

오류가 발생하는 원인도 제대로 알려주지 않고…. 뭔가 허접의 기운이 느껴지지만 …

아니, 뭐.. 어쨌든 문제 해결!

자작 스피커 받침대



이사오기 전에 구입한 2.1채널 스피커인 JBL Bar 2.1 Deep Bass 사운드바.

별생각 없이 잘 쓰고 있었는데, 이사온 뒤로 부쩍 층간소음이 신경쓰이면서 이넘의 서브우퍼가 걱정되기 시작한다.

서브우퍼를 켜 놓은 상태로 사운드가 빵빵한 영화라도 볼라치면 웬지 온 집안이 울리는 듯한 느낌적인 느낌…

뭔가 대책이 필요해…

효과가 있다, 없다 의견이 분분하긴한데, 우퍼나 스피커 하단에 방진패드 같은 걸 두면 없는거 보다는 낫겠지 하는 마음에 이리저리 검색을 해보는데…

뭐지… 우퍼 크기에 맞는 녀석이 없다;;;; 변태크기 였던 것인가….

그러던중 제품중에 자작나무합판을 덧덴 녀석이 있어서 검색을 좀 해봤더니

자작나무합판이 흡음 또는 차음 효과가 있다네.

그래? 그럼 하나 만들지 하고 냉큼 30T 짜리 자작나무합판을 사이즈에 맞춰 주문했다.

오~ 자작합판은 처음인데 무늬가 예쁘다.

우선은 폭풍 사포질~

…하다가 어차피 한면은 가릴거고 한면은 차음제를 붙일거라 적당히함.

사포질이 끝난 상태(두 개를 겹쳐 쓰려고 두 개를 주문했다).

그래도 바니쉬 칠은 해줘야지.

1차 바니쉬칠이 끝난 후 간단히 사포질 해주고,

한번 더 바니쉬 칠하고 사포질 완료.

이제는 한쪽 면에 붙일 차음제를 제단한디.

분명 치수를 재고 잘랐는데 사이즈가 안 맞다;;;

아무튼 차음제를 한쪽면에 붙인 후 사포로 다듬어 줬다.

자작합판을 흡음제로 생각하고 “흡음+차음+흡음+차음” 구성을 할려고 한건데,

자작합판이 흡음제 역할을 하는건지 모르겠네;;;

바닥에서 띄워주기 위해 고무발도 달아줌.

이제 두 녀석을 겹쳐주면…

완성!

효과는…..?

글쎄 다른집에 가서 비교를 해볼수도 없으니… 어떤지 알수가 없다.

다른 방에서 전보다 잘 안들리는 거 같기도 하고… 기분탓인가…

아무튼 없는거 보다는 낫겠지라는 믿음으로..

오늘도 스피커 볼륨을 줄이고 있음;;;

안드로이드앱이 항상 시작되도록 하기

근 1년 전에 안드로이드앱이 항상 실행되어 있도록 하기 위해서 썼던 방법.
(그 때 당시 자동 업데이트 기능으로 업데이트 후 다시 실행하는 것도 이 방법으로 해결했음)

우선 Activity 의 인스턴스가 1개만 존재하도록 해야 하므로 메인 activity의
lanuchMode를 singleTask로 설정해 준다.

<activity
            android:name=”.SampleActivity”
            android:label=”@string/app_name” android:theme=”@android:style/Theme.NoTitleBar.Fullscreen”
            android:launchMode=”singleTask”>

그리고, Service 를 하나 추가하고, Service에서 Alarm 으로  메인 Activity에 주기적으로 Intent를 날리도록 한다.

public class SampleService extends Service
{
    private static final int ALARM_INTERVAL = 30000;
    private boolean bRunning = false;

    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!bRunning) {
            bRunning = true;                        
                       
            final Intent alarmIntent = new Intent(this, SampleActivity.class);
            final PendingIntent pender = PendingIntent.getActivity(this, 0, alarmIntent, 0);
                       
            long triggerTime = SystemClock.elapsedRealtime() + 1000*30;
                       
            final AlarmManager alarm = (AlarmManager) this
                                            .getSystemService(Context.ALARM_SERVICE);
            alarm.setRepeating(AlarmManager.ELAPSED_REALTIME, triggerTime,
                               ALARM_INTERVAL, pender);
        } else {
            Log.e(“hadpan”, “sample service thread is already running”);
        }

        return START_STICKY;
    }

}

해당 Activity가 singleTask 로 설정되었기 때문에 Activity가 존재 한다면, 화면이 활성화 되고,
아직 만들어지기 전이라면 새로 앱을 실행하게 된다.

alarm 주기는 필요에 따라 적당히 조절하면 되는데.. 사실 이런 alram을 쓰는거 자체가 별로 권장하는 방법은 아니다.
당시, 만들던 앱 자체가 단말기를 전용 단말기 처럼.. 그러니까 단말기에서 해당 앱만 동작하면 되는 형태여서..
(일종의 키오스크 형태)

이 방법을 사용했지만 그다지 좋은 방법이라고는 생각되지 않음…=_=

Windows에서 Eclipse CDT 로 EFL 개발 하기

필요한건 EFL 라이브러리, MinGW, Eclipse CDT 세 가지다.

MinGW, Eclipse CDT의 설치는 생략 하기로 하고….

우선, http://dev.enlightenment.fr/~doursse/NSIS/ 에서 윈도우용 설치파일을 다운 받는다.

현재 최신 버전은 1.7.4 이다.

설치파일을 실행한 후 대부분 [Next] 를 클릭해 주면 되는데.. 다음 단계에서는 주의!

사용자 삽입 이미지

처음에는 다 선택된 거 처럼 보이지만(필자는 그런줄 알았음=_=), 스크롤바를 내려보면 [EFL binaries] 만

선택이 되어 있다는걸 알수 있다. 왜 디폴트로 라이브러리들을 다 설치 안하는 걸로 해놨는지는 의문이다.

머가 먼지 잘 모르니까 그냥 처음부터 끝까지 다 클릭해서 체크를 한 다음 [Next] 를 클릭해 준다.

사용자 삽입 이미지

나머지는 역시 [Next]를 클릭해 주면 별 문제 없이 설치가 완료된다.

이제 Eclipse를 실행 하고 프로젝트를 만들어 보자. Eclipse에는 CDT가 설치 되어 있어야 한다.

프로젝트는 C Project 로 만들고, [Project Type]은 [Excutable] -> [Empty Project],

[Toolchains] 는 [MinGW GCC]를 선택해 주면 된다.

EFL 을 사용하기 위해서는 헤더파일과 라이브러리 설정을 추가해 주어야 한다.

[Project Explorer] 에서 방금 만든 프로젝트를 선택하고, 마우스 오른쪽 버튼를 누른 다음,

[Properties]를 클릭한다.

왼쪽 트리에서 [C/C++ General] -> [Paths and Symbols]를 선택한다.

[Includes] 탭을 선택하고 [Add…] 버튼을 클릭하여 다음 경로들을 추가 해준다.

여기서 C:\Efl 은 아까 EFL 라이브러리를 설치한 경로이니, 경로를 변경한 경우에는 바꿔서 적어주면 된다.

C:\Efl\include\elementary-1
C:\Efl\include\eina-1
C:\Efl\include\eet-1
C:\Efl\include\evas-1
C:\Efl\include\ecore-1
C:\Efl\include\edje-1
C:\Efl\include\evil-1
C:\Efl\include\e_dbus-1
C:\Efl\include\dbus-1.0
C:\Efl\lib\dbus-1.0\include
C:\Efl\include\efreet-1
C:\Efl\include\ethumb-1

이 때, 상단의 [Configuration]를 [ All Configurations ] 로 변경하고 추가 해주면,

Debug와 Release에 모두 추가된다.

일일이 추가 해야 되서 좀 귀찮으면 다이얼로그 하단에 보이는 [Import Settings…] 를 클릭해서

다음 파일을 import 해주면된다.

1280029520.xml

파일을 선택한 다음, 왼쪽에서는 설정을 추가할 프로젝트, 오른쪽에서는 설정을 추가할 configuration을

선택해 주면 된다. Debug와 Release를 동시에 할 수 없으니, 따로 두 번 해 주어야 한다.

사용자 삽입 이미지

다음은 [Library Paths]을 선택하고 [Add…] 버튼을 클릭해서 다음 경로를 추가 해준다.

C:\Efl\lib

마지막으로 [Libraries] 을 선택해서 [Add…] 버튼을 클릭하고, 사용할 라이브러리 를 추가 해준다.

앞에 까지는 항상 동일하지만, 이 탭은 프로젝트에서 실제 사용하는 라이브러리에 따라 달라질 수 있다.

머, 걍 다 써줘도 상관 없지만… 이 탭의 설정을 설정파일로 import도 안되기 때문에, 필요한 거만 써주는게 좋겠다.

EFL 중에서 사용하는 라이브러리 이름을 추가해 주면 되는데 예를 들면, ecore, evil, elementrary 같은 식이다.

사용자 삽입 이미지






elementary, evil, evas 등 은 거의 항상 들어 갈것이다.

이제 [OK] 버튼을 누르고, 코드를 작성한 후 빌드해주면 된다.

리눅스에서는 pkg-config 인가 하는 프르그램을 써서 일일이 헤더 경로나 라이브러리 지정을 안하고 쓰는거 같던데..

윈도에서도 그런 방법이 없는지는… 모르겠다=_=;