DeepintoPharo:Chapter 09

From 흡혈양파의 번역工房
Jump to navigation Jump to search
제 9 장 Metacello를 이용해 프로젝트 관리하기

Metacello를 이용해 프로젝트 관리하기

Dale Henrichs (dale.henrichs@gemstone.com) 작성. 그리고
Mariano Martinez Peck (marianopeck@gmail.com) 참여


프로젝트를 로딩하려는데 없어진 줄도 몰랐던 패키지 때문에 오류가 발생했던 적이 있는가? 아니면 그보다 더 심각하게, 패키지가 있지만 올바르지 않은 버전이었던 경우가 있는가? 개발자들이 당신과 다른 컨텍스트에서 작업하는데 개발자들에게는 프로젝트가 양호하게 로딩되다가 자신에게만 이러한 상황이 발생하는 경우도 흔하다.


이러한 문제에 대한 프로젝트 개발자들의 해결책은 프로젝트를 구성하는 패키지들 간 의존성을 명시적으로 관리하도록 패키지 의존성 관리 시스템을 사용하는 것을 들 수 있다. 본 장은 Metacello, 즉 Pharo의 패키지 관리 시스템을 이용하는 방법과 이를 사용 시 이점에 대해 소개하겠다.


서론

Metacello는 Monticello용 패키지 관리 시스템이라고 말을 한다. 하지만 이게 정확히 어떤 의미일까? 패키지 관리 시스템은 소프트웨어 패키지 그룹을 설치, 업그레이드, 구성, 제거하는 과정을 자동화하는 툴의 집합체이다. Metacello는 패키지를 그룹화하여 사용자를 위해 작업을 간소화하고 의존성을 관리하여 가령 어떤 구성요소의 어떤 버전을 로딩해야 패키지 전체 집합이 일관될 수 있는지 관리한다.


패키지 관리 시스템은 패키지를 설치하는 일관된 방식을 제공한다. 뿐만 아니라 패키지 관리 시스템은 가끔 인스톨러(installer)로 불리곤 하는데, 이는 부정확하다. 오해를 야기할 수 있는데, 패키지 관리 시스템은 소프트웨어의 설치에 그치지 않고 설치 이상으로 많은 일을 실행하기 때문이다. 패키지 관리 시스템은 Envy (VisualAge Smalltalk), Maven (Java), apt-get/aptitude(Debian과 Ubuntu)와 같은 다른 컨텍스트에서 사용해보았을 것이다.


패키지 관리 시스템의 주요 특징 중 하나로, 어떤 패키지도 올바르게 로딩한다는 점을 들 수 있는데, 어떤 것도 수동으로 설치할 필요가 없다. 이것이 가능하려면 각 의존성, 의존성들의 의존성 등이 패키지의 설명에 명시되어야 하고, 이러한 설명은 패키지 관리 툴이 그들을 올바른 순서로 로딩시키기에 충분한 정보를 포함해야 한다.


Metacello의 장점을 보여주는 예로 PharoCore 이미지를 들 수 있는데, 의존성의 문제 없이 어떤 프로젝트의 어떤 패키지든 로딩할 수 있다. 물론 Metacello는 마법(magic)을 사용하지 않으며, 마법은 패키지 개발자들이 의존성을 올바로 정의하는 경우에 한에서만 효과가 있다.


각 작업마다 하나의 툴

Pharo는 소프트웨어 패키지를 관리하는 세 개의 툴을 제공하는데, 모두 밀접하게 연관되어 있으나 각각이 고유의 목적을 지닌다. 툴은 소스 코드의 버전을 관리하는 Monticello, Monticello에 대한 스크립팅 인터페이스 Gofer, 패키지 관리 시스템인 Metacello로 구성된다.


Monticello: 소스 코드 버저닝. 소스 코드 버저닝은 유일한 버전을 특정 소프트웨어 상태로 할당하는 과정이다. 이는 새 버전을 커밋(commit)하고, 타인이 커밋한 버전으로 업데이트하며, 버전들 간 차이를 살펴보고, 오래된 버전으로 복구(revert)하는 작업 등을 실행할 수 있도록 해준다.
Pharo는 Monticello 패키지를 관리하는 Monticello 소스 코드 버저닝 시스템을 이용한다. Monticello는 각 패키지마다 위의 연산을 모두 실행하도록 해주지만 패키지들 간 의존성을 쉽게 명시하거나, 패키지의 안정된 버전을 식별하거나, 패키지를 의미 있는 단위로 그룹화하는 방식은 Monticello에서 제공하지 않는다. 이와 관련해 제 7장에서 설명한 바가 있다.

Gofer: Monticello의 스크립팅 인터페이스. Gofer는 Monticello에 포함된 작은 툴로서, Monticello 패키지의 그룹을 로딩, 업데이트, 병합, 구별, 복구, 커밋, 재컴파일, 언로딩한다. Gofer는 또 이러한 연산들이 가능한 한 깔끔하게 실행되도록 보장한다. 상세한 정보는 제 8장을 참고한다.

Metacello: 패키지 관리. Metacello는 프로젝트의 개념을 연관된 Monticello 패키지의 집합으로 소개한다. 이는 프로젝트, 프로젝트의 의존성, 메타데이터를 관리하는 데 사용된다. Metacello는 패키지들 간 의존성을 관리하기도 한다.


Metacello 특징

Metacello는 Monticello의 중요한 기능과 일치하며, 아래와 같은 개념을 바탕으로 한다.


선언적 프로젝트 설명. Metacello 프로젝트는 Monticello 패키지의 리스트로 구성된 버전을 versions으로 명명하였다. 의존성은 필요한 프로젝트의 명명된 버전과 관련해 명시적으로 표현된다. 필요한 프로젝트라 함은 또 다른 Metacello 프로젝트에 대한 참조다. 총괄적으로 이러한 설명을 모두 프로젝트 메타데이터라고 부른다.

프로젝트 메타데이터는 버저닝된다. Metacello 프로젝트 메타데이터는 클래스 내 인스턴스 메서드로서 표현된다. 그러한 메타데이터를 코드로서 관리하면 대부분 패키지 관리 시스템이 사용하는 XML과 비교 시 엄청난 장점을(power) 가져온다. Metacello 프로젝트 메타데이터 자체가 Monticello 패키지로서 보관 가능하고, 그에 따라 버전 제어의 대상이 된다. 그 결과 프로젝트 메타데이터로의 동시적 업데이트를 쉽게 관리할 수 있는데, 코드 베이스의 병렬 버전과 마찬가지로 메타데이터의 병렬 버전(parallel version)도 병합이 가능하기 때문이다.


Metacello의 특징은 다음과 같다.


크로스 플랫폼: Metacello는 Monticello를 지원하는 모든 플랫폼에서 실행되고, 현재로선 Pharo, Squeak, GLASS가 해당된다.

조건적 패키지 로딩: 프로젝트를 다수의 플랫폼에서 실행시키기 위해 Metacello 는 플랫폼 특정적 Monticello 패키지의 조건적 로딩을 지원한다.

설정(configurations): Metacello는 프로젝트의 설정을 관리한다. 대규모 프로젝트에는 각 플랫폼에 요구되는 여러 프로젝트와 패키지 집합으로 된 다수의 variations가 있는 것이 보통이다. 유일한 설정마다 버전 문자열이 표시된다.


Metacello는 두 가지 유형의 엔티티, baselines 와 versions 의 정의를 (메서드로서 표현) 지원한다.


baseline. baseline은 프로젝트의 기본 구조를 정의한다. baseline은 프로젝트를 구성하는 데 요구되는 프로젝트와 패키지를 열거한다. baseline은 패키지가 로딩되어야 하는 순서를 정의하고 패키지가 로딩되는 저장소를 정의한다.

그림 9.1: 설정: 의존성과 함께 한 버전과 baseline의 그룹


Versions(버전). 버전은 로딩해야 하는 각 프로젝트와 패키지의 정확한 버전을 식별한다. 버전은 baseline 버전을 기반으로 한다. baseline 버전 내 각 패키지마다 Monticello 파일명(예: Metacello-Base-dkh.152)이 명시된다. baseline 버전 내 각 프로젝트마다 Metacello 버전 번호가 명시된다.


그림 9.1의 ConfigurationOfProjecta 는 2개의 baseline(baseline 0.4와 0.5)과 4개의 버전(버전 0.4, 0.4.1, 0.5, 0.6)을 포함한다. 버전 0.4는 baseline 0.4를 기반으로 하며, 각 패키지마다 버전을 명시한다 (PacageAversion.5과 PackageB-version.3). 버전 0.4.1 또한 baseline 0.4를 기반으로 하지만 PackageA에 대한 다른 버전을 명시한다 (Package-version.7).


Baseline 0.5는 3개의 패키지(PackageA, PackageB, PackageC)로 구성되며, 외부 프로젝트(ProjectB)에 의존한다. 새 패키지(PackageC)와 프로젝트 의존성(ProjectB)이 프로젝트에 추가되어 새 구조를 반영하는 새 baseline 버전이 생성되어야 한다. 버전 0.5는 baseline 0.5를 기반으로 하며, 패키지의 버전(PackageA-version.6, PackageB-version.4, PackageC-version.1)과 의존적 프로젝트의 버전(ProjectB-version3)을 명시한다.


간단한 사례 연구

이 예제에서는 버전으로만 표현된 간단한 구성으로 시작해 점차적으로 baseline을 추가하고자 한다. 실제로는 baseline을 정의한 다음에 버전을 정의하는 편이 낫다.


그렇다면 CoolBrowser라고 불리는 소프트웨어 프로젝트를 관리하는 데에 Metacello를 이용한다고 치자. 첫 번째 단계는 MetacelloConfigTemplate 클래스를 복사한 후 클래스명을 오른쪽 마우스로 클릭하여 copy 를 선택하거나 Monticello 브라우저의 +Config 을 이용해 ConfigurationOfCoolBrowser로 명명함으로써 프로젝트에 대한 Metacello 설정을 생성하는 것이다 (제 7장). 설정(configuration)은 프로젝트에 현재 이용할 수 있는 설정(baseline과 버전 집합)을 설명하는 클래스로서 앞에서는 메타데이터라고 불렀던 것에 해당한다. 설정은 프로젝트의 여러 버전을 표현하여 Pharo의 다른 환경이나 버전에서 프로젝트를 로딩할 수 있도록 한다. 관습상 Metacello 설정의 이름은 ConfigurationOf를 앞에 붙여 구성된다.


클래스 정의는 다음과 같다.

Object subclass: #ConfigurationOfCoolBrowser
    instanceVariableNames: 'project'
    classVariableNames: 'LastVersionLoad'
    poolDictionaries: ''
    category: 'Metacello-MC-Model'


ConfigurationOfCoolBrowser에 인스턴스 측과 클래스 측 메서드가 있음을 눈치챌 것인데 이들이 어떻게 사용되는지는 후에 살펴보도록 하겠다. 이 클래스는 Object로부터 상속된다는 사실도 주목하라. Metacello 설정은 Metacello 자체를 포함해 어떤 전제조건도 없이 로딩이 가능하므로 Metacello 설정은 공통 슈퍼클래스에 의존할 수 없다.


이제 CoolBrowser 프로젝트에 여러 버전, 즉 1.0, 1.0.1, 1.4, 1.67과 같은 버전이 있다고 가정해보자. Metacello를 이용해 설정(configuration) 메서드, 각 프로젝트 버전의 내용을 설명하는 인스턴스 측 메서드를 생성한다. 아래와 같이 메서드에 <version:> pragma로 주석이 달려있는 한 버전 메서드에 대한 메서드명은 중요하지 않다. 하지만 버전 메서드가 versionXXX: 로 명명되는 관습도 있는데, 여기서 XXX는 틀린 문자(예: '.')가 없는 버전 번호를 의미한다.


CoolBrowser가 CoolBrowser-Core와 CoolBrowser-Tests라는 두 개의 패키지를 포함한다고 치자 (그림 9.2). 설정 메서드(여기서는 버전)는 아마 다음과 같은 모습일 것이다.

그림 9.2: 간단한 버전.

ConfigurationOfCoolBrowser>>version01: spec
    <version: '0.1'>

    spec for: #common do: [
    spec blessing: #release.
        spec repository: 'http://www.example.com/CoolBrowser'.
        spec
            package: 'CoolBrowser-Core' with: 'CoolBrowser-Core-BobJones.10';
            package: 'CoolBrowser-Tests' with: 'CoolBrowser-Tests-JohnLewis.3' ]


version01:spec 메서드는 spec 객체에 프로젝트의 0.1 버전에 대한 설명을 빌드한다. 버전 0.1에 대한 공통 코드는 (for:do: 메시지를 이용해 명시) CoolBrowser-Core와 CoolBrowser-Tests로 명명된 특정 패키지의 버전으로 구성된다. 이들은 package:packageName with: versionName 메시지를 이용해 명시된다. 이러한 버전들은 http://www.example.com/CoolBrowser의 Monticello 저장소에서 이용 가능하며, repository: 메시지를 이용해 명시된다. blessing: 메서드는 이것이 출시(released) 버전이며 명세는 추후에도 변경되지 않을 것임을 의미한다. 버전이 안정화되지 않았을 때에는 #development를 사용해야 한다.


이제 좀 더 자세히 살펴보도록 하자.

  • 메서드 선택자 바로 다음에 pragma 정의, <version:'0.1'>가 보일 것이다. version: pragma는 해당 메서드에 생성된 버전이 CoolBrowser 프로젝트의 0.1 버전과 연관되어야 함을 의미한다. 이것이 바로 메서드명이 중요하지 않다고 언급한 이유다. Metacello는 정의되는 버전을 식별하는 데에 메서드명이 아니라 pragma를 이용한다.
  • 메서드의 인자, spec은 메서드에서 유일한 변수로서 4개의 메시지, for:do:, blessing:, package:with:, repository: 의 수신자로서 사용된다.
  • 블록이 메시지의 (for:do:, package:with:, ...) 인자로서 전달될 때마다 새 객체가 스택으로 삽입되고 블록 내 메시지들은 스택의 젤 위에 위치한 객체로 전송된다.
  • #common 기호는 해당 프로젝트 버전이 모든 플랫폼에 공통적임을 의미한다. #common 외에도 Metacello가 실행되는 플랫폼마다 (#pharo, #squeak, #gemstone, #squeakCommon, #pharo, #pharo1.3.x 등등) 사전에 정의된 속성들이 존재한다. Pharo에서 metacelloPlatformAttributes 메서드가 당신이 사용할 수 있는 속성 값을 정의한다.


비밀번호에 관하여. 때로는 Monticello 저장소가 사용자명과 비밀번호를 요구하기도 한다. 이런 경우, repository: 대신 repository:username:password: 메시지를 이용할 수 있다.

spec repository: 'http://www.example.com/private' username: 'foo' password: 'bar'


명세(specification) 객체. spec 객체는 주어진 버전에 관한 모든 정보를 표현하는 객체다. 버전은 숫자에 불과한 반면 명세는 객체다. spec 메시지를 이용해 명세로 접근할 수 있다 (보통은 불필요하다).

(ConfigurationOfCoolBrowser project version: '0.1') spec


이는 '0.1' 버전을 정의하는 메서드 정보를 정확히 포함하는 객체(MetacelloMCVersionSpec 클래스의 인스턴스)를 응답한다.


새 버전 생성하기. 우리 프로젝트의 0.2 버전이 패키지 버전 CoolBrowser-Core-BobJones.15와 CoolBrowser-Tests-JohnLewis.8, 그리고 CoolBrowser-Addons-JohnLewis.3의 버전을 가진 새 패키지 CoolBrowser-Addons로 구성된다고 가정해보자. 이러한 새 설정은 다음과 같이 version02: 로 명명된 메서드를 생성하여 명시할 수 있겠다.

ConfigurationOfCoolBrowser>>version02: spec
    <version: '0.2'>

    spec for: #common do: [
        spec repository: 'http://www.example.com/CoolBrowser'.
        spec
            package: 'CoolBrowser-Core' with: 'CoolBrowser-Core-BobJones.15';
            package: 'CoolBrowser-Tests' with: 'CoolBrowser-Tests-JohnLewis.8';
            package: 'CoolBrowser-Addons' with: 'CoolBrowser-Addons-JohnLewis.3']


다수의 저장소를 관리하는 방법. 다수의 저장소를 spec으로 추가할 수도 있다. repository: 표현식을 여러 번 명시하면 될 일이다.

그림 9.3: 프로젝트의 두 가지 버전.

ConfigurationOfCoolBrowser>>version02: spec
    ...

    spec for: #common do: [
        spec repository: 'http://www.example.com/CoolBrowser'.
        spec repository: 'http://www.anotherexample.com/CoolBrowser'.
        ...
        ]


repositoryOverrides 메시지를 사용하는 수도 있다.

self whateverVersion repositoryOverrides: (self whateverRepo); load


이러한 메시지들은 의존적 설정으로 재귀적으로 퍼지지 않음을 주목하라.


설정 명명하기. 앞에서 우리는 설정 클래스를 명명하는 규칙을 살펴보았다. 우리 예제에서는 ConfigurationOfCoolBrowser가 해당한다. 설정 클래스와 이름이 동일한 Monticello 패키지를 생성하고, 클래스를 그 패키지로 넣는 방법도 있다. 따라서 이번 예제에선 하나의 클래스 ConfigurationOfCoolBrowser만 포함하는 패키지 ConfigurationOfCoolBrowser를 생성해볼 것이다.


패키지명과 설정 가능한 클래스명을 동일하게 만들고, ConfigurationOf 문자열로 시작하도록 만듦으로서 이용 가능한 프로젝트를 열거하는 저장소를 쉽게 스캔할 수 있다. 설정이 자신만의 Monticello 저장소에 보관되어도 매우 편리할 것이다.


Metacello 설정 로딩하기

물론 Metacello에서 프로젝트 설정을 명시하는 이유는 정확히 특정 설정을 자신의 이미지에 로딩하여 패키지 버전의 일관된 집합을 갖는 데에 있다. 버전을 로딩하기 위해서는 버전에 load 메시지를 전송해야 한다. CoolBrowser의 버전들을 로딩하는 예를 들어보자.

(ConfigurationOfCoolBrowser project version: '0.1') load.
(ConfigurationOfCoolBrowser project version: '0.2') load.


뿐만 아니라 각 표현식의 결과를 인쇄할 경우 로딩 순서로 된 패키지 목록이 표시된다는 점도 주목해야 하는데, Metacello는 어떤 패키지가 로딩되는지 뿐만 아니라 그 순서까지 관리하기 때문이다. 설정을 디버깅하기가 수월해질 것이다.


선택적 로딩. 기본적으로 load 메시지는 버전과 연관된 모든 패키지를 로딩한다 (후에 살펴보겠지만 이는 default 라는 특정 그룹을 정의하여 변경할 수 있다). 프로젝트에 패키지의 하위집합을 로딩하고 싶다면 관심 있는 패키지들의 이름을 load: 메서드에 대한 인자로서 열거해야 한다.

(ConfigurationOfCoolBrowser project version: '0.2') load:
    { 'CoolBrowser-Core' .
    'CoolBrowser-Addons' }.


설정 디버깅하기. 설정을 실제로 로딩하지 않고 로딩을 시뮬레이트하고 싶다면 load(혹은 load:) 대신 record(혹은 record:)를 사용해야 한다. 이후 시뮬레이션 결과를 얻기 위해서는 아래와 같이 loadDirective 메시지를 전송해야 한다.

((ConfigurationOfCoolBrowser project version: '0.2') record:
    { 'CoolBrowser-Core' .
    'CoolBrowser-Addons' }) loadDirective.


load 와 record 외에도 fetch(그리고 fetch:)라는 유용한 메서드가 있다. 설명한 바와 같이 record는 로딩되어야 하는 Monticello 파일과 그 순서를 단순히 기록하는 일만 한다. fetch는 필요한 Monticello 파일로 모두 접근하여 다운로드한다. 확실히 말해두지만 implementation에서 load는 fetch를 먼저 실행한 다음 doLoad를 실행한다.


패키지 간 의존성 관리하기

프로젝트는 주로 여러 개의 패키지로 구성되고, 이 패키지들은 종종 다른 패키지로 의존성을 갖는다. 특정 패키지는 다른 패키지의 특정 버전에 의존하기 쉽다. 의존성을 올바로 처리하는 것이 매우 중요하며 Metacello의 주요 이점에 해당하기도 한다. 의존성에는 두 가지 유형이 있다.


내적 의존성. 프로젝트 내에는 여러 개의 패키지가 포함되어 있으며, 그 중 일부는 동일한 프로젝트의 여러 패키지에 의존한다.

프로젝트 간 의존성. 프로젝트가 다른 프로젝트에 의존하거나, 다른 프로젝트로부터 비롯된 일부 패키지에 의존하는 것은 흔한 일이다. 가령 Pier(meta-described 내용 관리 시스템)는 Magritte(메타데이터 모델링 프레임워크)와 Seaside(웹 애플리케이션 개발용 프레임워크)에 의존한다.


지금은 내적 의존성에 중점을 둘 것인데, 그림 9.4에서 설명한 바와 같이 CoolBrowser-Tests와 CoolBrowser-Addons 패키지 모두 CoolBrowser-Core에 의존한다고 가정하자. 버전 0.1과 0.2에 대한 명세는 이러한 의존성을 포착하지 않았다. 새 설정은 다음과 같다.

ConfigurationOfCoolBrowser>>version03: spec
    <version: '0.3'>

    spec for: #common do: [
        spec repository: 'http://www.example.com/CoolBrowser'.
        spec
            package: 'CoolBrowser-Core' with: 'CoolBrowser-Core-BobJones.15';
            package: 'CoolBrowser-Tests' with: [
                spec
                    file: 'CoolBrowser-Tests-JohnLewis.8';
                    requires: 'CoolBrowser-Core' ];
            package: 'CoolBrowser-Addons' with: [
                spec
                    file: 'CoolBrowser-Addons-JohnLewis.3';
                    requires: 'CoolBrowser-Core' ]].


version03: 에서 requires: 지시어를 이용해 의존성 정보를 추가하였다.


또한 패키지의 특정 버전을 참조하는 file: 메시지를 소개하였다. CoolBrowser-Tests와 CoolBrowser-Addons는 그들이 로딩되기 전에 로딩되어야 하는 CoolBrowser-Core를 필요로 한다. 그들이 의존하는 Cool-Browser-Core의 정확한 버전을 명시하지는 않았음을 주목하라. 이는 문제를 야기할 수 있으나 그 결함은 조만간 다룰 것이니 걱정하지 말길 바란다!


이 버전을 이용하면 구조적 정보(필요한 패키지와 저장소)와 버전 정보(정확한 번호 버전)를 결합한다. 시간이 지나면서 버전 정보는 자주 변경될 것이지만 구조적 정보는 다소 같은 상태로 유지될 것이라고 예상할 수 있다. 이를 포착하기 위해 Metacello는 Baselines란 개념을 도입한다.

그림 9.4: 버전 0.3은 동일한 프로젝트 내 패키지들 간 내적 의존성을 표현한다.


Baselines

baseline이란 패키지나 프로젝트 간 구조적 의존성에 관한 프로젝트의 아키텍처 또는 뼈대를 의미한다. baseline은 패키지명만 이용해 프로젝트의 구조를 정의한다. 구조가 변경되면 baseline은 업데이트되어야 한다. 구조적 변경이 없다면 변경은 baseline에서 패키지의 특정 버전을 고르는 것으로 제한된다.


이제 우리 예제를 계속해보자. 가장 먼저 baseline을 이용해 수정할 것인데, baseline에 대한 메서드를 하나 생성하겠다. 메서드명과 버전 pragma는 어떤 형태든 취할 수 있음을 명심한다. 하지만 가독성을 위해서는 둘 다 'baseline'을 추가해야 한다. 의무적인 것은 blessing: 메시지의 인자인데, 이는 baseline을 정의한다.

ConfigurationOfCoolBrowser>>baseline04: spec "convention"
    <version: '0.4-baseline'> "convention"

    spec for: #common do: [
        spec blessing: #baseline. "mandatory to declare a baseline"
        spec repository: 'http://www.example.com/CoolBrowser'.
        spec
            package: 'CoolBrowser-Core';
            package: 'CoolBrowser-Tests' with: [ spec requires: 'CoolBrowser-Core'];
            package: 'CoolBrowser-Addons' with: [ spec requires: 'CoolBrowser-Core']]


baseline04: 메서드는 여러 버전에 의해 사용되는지도 모르는 0.4-baseline의 구조를 정의한다. 예를 들어 아래에 정의된 버전 0.4는 그림 9.5에서 보이는 바와 같이 이러한 구조를 이용한다. baseline은 저장소, 패키지, 그 패키지들 간 의존성을 명시하지만 패키지의 구체적 버전을 명시하지는 않는다.

그림 9.5: 버전 0.4는 이제 패키지 간 의존성을 표현하는 baseline을 가져온다(import).


baseline과 관련해 버전을 정의하기 위해 아래와 같이 pragma <version:imports:>를 사용한다.

ConfigurationOfCoolBrowser>>version04: spec
    <version: '0.4' imports: #('0.4-baseline')>

    spec for: #common do: [
        spec
            package: 'CoolBrowser-Core' with: 'CoolBrowser-Core-BobJones.15';
            package: 'CoolBrowser-Tests' with: 'CoolBrowser-Tests-JohnLewis.8';
            package: 'CoolBrowser-Addons' with: 'CoolBrowser-Addons-JohnLewis.3'
    ].


version04: 메서드에서 우리는 패키지의 구체적인 버전을 명시한다. version:imports: pragma는 해당 버전(버전 '0.4')이 기반으로 하는 버전의 목록을 명시한다. 구체적 버전이 명시되면 그것이 baseline을 사용한다는 사실과 상관없이 이전과 같은 방식으로 로딩된다.

(ConfigurationOfCoolBrowser project version: '0.4') load.

그림 9.6: 두 번째 버전(0.5)은 버전 0.4와 같은 baseline을 가져온다.


Baseline 로딩하기

버전 0.4-baseline이 명시적인 패키지 버전 정보를 포함하지 않는다 하더라도 로딩하는 방법은 있다!

(ConfigurationOfCoolBrowser project version: '0.4-baseline') load.


로더가 버전 정보가 없는 패키지를 마주치면 저장소로부터 패키지의 가장 최신 정보를 로딩하고자 시도한다.


때때로, 그 중에서도 여러 개발자가 프로젝트를 작업할 때는 모든 개발자들의 최신 작업으로 접근하기 위해 baseline 버전을 로딩하는 편이 유용하다. 이러한 경우 baseline 버전은 정말로 "최첨단" 버전이 된다.


새 버전 선언하기. 이제 프로젝트의 새 버전, 즉 버전 0.4와 구조는 같지만 패키지의 다른 버전들을 포함하는 버전 0.5를 생성한다고 가정하자. 동일한 baseline을 가져옴으로써 이러한 계획을 담아낼 수 있는데, 이 관계는 그림 9.6에 설명되어 있다.

ConfigurationOfCoolBrowser>>version05: spec
    <version: '0.5' imports: #('0.4-baseline')>

    spec for: #common do: [
    spec
        package: 'CoolBrowser-Core' with: 'CoolBrowser-Core-BobJones.20';
        package: 'CoolBrowser-Tests' with: 'CoolBrowser-Tests-JohnLewis.8';
        package: 'CoolBrowser-Addons' with: 'CoolBrowser-Addons-JohnLewis.6' ].


큰 프로젝트에 대한 baseline을 생성 시 보통은 어느 정도 시간과 노력이 요구되는데 모든 패키지의 모든 의존성 뿐만 아니라 다른 것들도 포착해야 하기 때문이며, 그러한 것들은 후에 살펴보도록 하겠다. 하지만 baseline이 정의되고 나면 프로젝트의 새 버전을 생성하는 과정이 크게 단순화되고 소요되는 시간도 줄어든다.


그룹

CoolBrowser 프로젝트가 증가하여 개발자가 CoolBrowser-Addons에 대한 테스트를 몇 가지 작성한다고 가정해보자. 이는 그림 9.7과 같이 CoolBrowser-Addons와 CoolBrowser-Tests에 의존하는 CoolBrowser-AddonsTests라는 새 패키지를 구성한다.


테스트의 유무와 상관없이 프로젝트를 로딩하길 원하는경우, 아래와 같이 모든 테스트 패키지를 명시적으로 열거하는 대신,

(ConfigurationOfCoolBrowser project version: '0.6')
    load: #('CoolBrowser-Tests' 'CoolBrowser-AddonsTests').


아래와 같은 간단한 표현식으로 모든 테스트를 로딩할 수 있다면 편리하겠다.

(ConfigurationOfCoolBrowser project version: '0.6') load: 'Tests'.


Metacello는 그룹이란 개념을 제공한다. 그룹은 항목(item)의 집합체로, 각 항목은 패키지, 프로젝트, 또는 심지어 다른 그룹이 될 수도 있다.


그룹은 다양한 목적으로 항목의 집합을 명명하도록 해주기 때문에 유용하다. 사용자에게 core만 설치하거나 add-on와 개발 기능이 있는 core를 설치할 수 있는 기회를 제공하여 적절한 그룹을 정의하는 일을 수월하게 만들 수 있길 원할지도 모르겠다. 그림 9.7의 예제로 돌아가 새 baseline, 즉 6개 그룹을 정의하는 0.6-baseline 을 어떻게 정의하는지 살펴보자. 이 예제에서 우리는 CoolBrowser-Tests와 CoolBrowser-AddonsTests를 구성하는 Tests라는 그룹을 생성한다.


그룹을 정의하기 위해 group: groupName with: groupElements 메서드를 이용한다. with: 인자는 패키지명, 프로젝트, 다른 그룹, 또는 이러한 것들의 집합체가 되기도 한다. 그림 9.7에 해당하는 코드는 다음과 같다.

그림 9.7: 6개 그룹, baseline: default, Core, Extras, Tests, CompleteWithoutTests, CompleteWithTests이 있는 baseline.

ConfigurationOfCoolBrowser>>baseline06: spec
    <version: '0.6-baseline'>
    spec for: #common do: [
        spec blessing: #baseline.
        spec repository: 'http://www.example.com/CoolBrowser'.
        spec
            package: 'CoolBrowser-Core';
            package: 'CoolBrowser-Tests' with: [ spec requires: 'CoolBrowser-Core' ];
            package: 'CoolBrowser-Addons' with: [ spec requires: 'CoolBrowser-Core' ] ;
            package: 'CoolBrowser-AddonsTests' with: [
                spec requires: #('CoolBrowser-Addons' 'CoolBrowser-Tests' ) ].
        spec
            group: 'default' with: #('CoolBrowser-Core' 'CoolBrowser-Addons');
            group: 'Core' with: #('CoolBrowser-Core');
            group: 'Extras' with: #('CoolBrowser-Addons');
            group: 'Tests' with: #('CoolBrowser-Tests' 'CoolBrowser-AddonsTests');
            group: 'CompleteWithoutTests' with: #('Core' 'Extras');
            group: 'CompleteWithTests' with: #('CompleteWithoutTests' 'Tests')
        ].


그룹은 baseline에서 정의된다. 그룹은 baseline 버전에서 정의할 것인데, 그룹이 구조적 구성 요소이기 때문이다. 기본 그룹은 다음 절들에 걸쳐 사용될 것임을 주목하라. 여기서 기본 그룹은 load 메서드가 사용되면 'CoolBrowser-Core'와 'CoolBrowser-Addons' 패키지가 로딩될 것임을 언급한다.


이제 CoolBrowser-AddonsTests라는 새 패키지가 추가된다는 점만 제외하면 해당 baseline을 이용해 0.5 버전과 동일한 버전 0.6을 정의할 수 있다.

ConfigurationOfCoolBrowser>>version06: spec
    <version: '0.6' imports: #('0.6-baseline')>

    spec for: #common do: [
        spec
            package: 'CoolBrowser-Core' with: 'CoolBrowser-Core-BobJones.20';
            package: 'CoolBrowser-Tests' with: 'CoolBrowser-Tests-JohnLewis.8';
            package: 'CoolBrowser-Addons' with: 'CoolBrowser-Addons-JohnLewis.6' ;
            package: 'CoolBrowser-AddonsTests' with: 'CoolBrowser-AddonsTests-JohnLewis.1' ].


예제. 그룹을 정의했다면 프로젝트나 패키지의 이름을 사용하는 곳이라면 어디서든 그룹명을 사용할 수 있다. load: 메서드는 패키지명, 프로젝트명, 그룹명, 또는 그러한 항목의 컬렉션명을 매개변수로 취한다. 따라서 아래의 문이 모두 가능하다.

(ConfigurationOfCoolBrowser project version: '0.6') load: 'CoolBrowser-Core'.
    "Load a single package"

(ConfigurationOfCoolBrowser project version: '0.6') load: 'Core'.
    "Load a single group"

(ConfigurationOfCoolBrowser project version: '0.6') load: 'CompleteWithTests'.
    "Load a single group"

(ConfigurationOfCoolBrowser project version: '0.6')
    load: #('CoolBrowser-Core' 'Tests').
    "Loads a package and a group"

(ConfigurationOfCoolBrowser project version: '0.6')
    load: #('CoolBrowser-Core' 'CoolBrowser-Addons' 'Tests').
    "Loads two packages and a group"

(ConfigurationOfCoolBrowser project version: '0.6')
    load: #('CoolBrowser-Core' 'CoolBrowser-Tests').
    "Loads two packages"

(ConfigurationOfCoolBrowser project version: '0.6') load: #('Core' 'Tests').
    "Loads two groups"


그룹 default와 'ALL'. default 그룹은 특별한 그룹이다. load 메시지는 default 그룹의 멤버들을 로딩하는 반면 ALL 그룹을 로딩하면 모든 패키지들이 로딩된다. 게다가 default는 기본 값으로 ALL을 로딩한다!

(ConfigurationOfCoolBrowser project version: '0.6') load.


이는 CoolBrowser-Core와 CoolBrowser-Addons만 로딩한다.


default 그룹이 있다면 프로젝트의 모든 패키지를 어떻게 로딩할 것인가? 아래와 같이 미리 정의된 ALL 그룹을 사용한다.

(ConfigurationOfCoolBrowser project version: '0.6') load: 'ALL'.


Core, Tests, 그리고 default에 관하여

설정에는 최소 두 개의 그룹, 즉 Core(실제 코드)와 Tests(연관된 테스트)가 있는 것이 보통이다. 문제는 default 그룹이 무엇을 로딩하느냐가 된다 (매개변수로 어떤 것도 명시하지 않을 경우 로딩되는 것임을 명심하라).


spec group: 'default' with: #('Core')라고 말하는 것은 우리는 기본적으로 tests를 로딩하지 않음을 말하는 것과 같다.


이제 어떤 default도 명시하지 않을 경우 기본 값으로 모든 것을 취하기 때문에 우리 예제의 spec group: 'default' with: #('Core' 'Tests')와 같을 것이다.


기본적으로 tests도 로딩하는 편이 낫다고 생각한다. 이것이 바로 default 그룹에 Tests 그룹을 명시적으로 넣거나 default를 아예 명시하지 않는 이유다.


프로젝트 간 의존성

패키지가 다른 패키지에 의존하는 것과 같이 프로젝트 또한 다른 프로젝트에 의존할 수 있다. 예를 들어, 내용 관리 시스템(CMS)에 해당하는 Pier는 Magritte와 Seaside에 의존한다. 프로젝트는 하나 또는 이상의 프로젝트의 엔티티, 다른 프로젝트로부터 패키지 그룹, 다른 프로젝트로부터 하나 또는 두 개의 패키지에 의존할 수 있다.

Metacello 설명 없이 프로젝트에 의존하기

프로젝트 X로부터 패키지 A가 프로젝트 Y의 프로젝트 B를 의존하고, 프로젝트 Y는 Metacello를 이용해 설명되지 않았다고 가정하자. 이런 경우 의존성을 아래와 같이 설명할 수 있겠다.

"In a baseline method"
spec
    package: 'PackageA' with: [ spec requires: #('PackageB')];
    package: 'PackageB' with: [ spec
        repository: 'http://www.smalltalkhub.com/ProjectB' ].
"In the version method"
package: 'PackageB' with: 'PackageB-JuanCarlos.80'.


어느 정도는 작동한다. 단, 이 접근법에서는 프로젝트 B가 Metacello 설정에 의해 설명되지 않기 때문에 B의 의존성이 관리되지 않는다는 결함이 있다. 즉, B 패키지의 어떤 의존성도 로딩되지 않을 것이다. 따라서 이런 경우 시간을 들여 프로젝트 B에 대한 설정을 생성할 것을 권한다.


Metacello 설정이 있는 프로젝트에 의존하기

이제 우리가 의존하는 프로젝트가 Metacello를 이용해 설명되는 경우를 생각해보자. CoolBrowser 프로젝트로부터 패키지를 이용하는 CoolToolSet라는 새 프로젝트를 소개해보자. 그 설정 클래스를 ConfigurationOfCoolToolSet이라고 부른다. CoolToolSet에는 CoolToolSet-Core와 CoolToolSet-Tests라고 불리는 두 개의 패키지가 있다고 가정하자. 이러한 패키지들은 CoolBrowser의 패키지에 의존한다.


CoolToolSet의 버전 0.1은 baseline을 가져오는 일반 버전에 불과하다.

ConfigurationOfCoolToolSet>>version01: spec
    <version: '0.1' imports: #('0.1-baseline')>
    spec for: #common do: [
        spec
            package: 'CoolToolSet-Core' with: 'CoolToolSet-Core-AlanJay.1';
            package: 'CoolToolSet-Tests' with: 'CoolToolSet-Tests-AlanJay.1'.].


당신이 의존하는 프로젝트가 관습을 따른다면 (예: ConfigurationOfCoolBrowser 패키지 내의 ConfigurationOfCoolBrowser 클래스) baseline의 정의는 간단하다. 기본적으로는 당신이 로딩하길 원하는 버전(versionString: 을 이용)과 프로젝트 저장소(repository: 이용)만 명시하면 된다.

ConfigurationOfCoolToolSet >>baseline01: spec
    <version: '0.1-baseline'>
    spec for: #common do: [
        spec repository: 'http://www.example.com/CoolToolSet'.
        spec project: 'CoolBrowser ALL' with: [
            spec
                repository: 'http://www.example.com/CoolBrowser';
                loads: #('Core' 'Tests');
                versionString: '2.5' ]
        spec
            package: 'CoolToolSet-Core' with: [ spec requires: 'CoolBrowser ALL' ];
            package: 'CoolToolSet-Tests' with: [ spec requires: 'CoolToolSet-Core' ]].


프로젝트 참조를 CoolBrowserALL이라고 명명했다. 프로젝트 참조 이름은 임의로 정해지며 원하는 대로 선택이 가능하지만 해당하는 프로젝트 참조에 의미가 통하는 이름으로 정하길 권한다. CoolToolSet-Core 패키지에 대한 명세에서 CoolBrowser ALL이 필요함을 명시하였다. 후에 설명하겠지만 project:with: 메시지는 로딩하길 원하는 프로젝트의 정확한 버전을 명시할 수 있도록 해준다.


load: 메시지는 로딩할 패키지 또는 그룹을 명시한다. load: 의 매개변수는 load의 매개변수와 같을 수도 있으며, 패키지명, 그룹명, 이러한 것들의 컬렉션명이 해당하겠다. load: 은 선택적으로 호출이 가능함을 주목해야 하는데, 기본 값과 다른 무언가를 로딩하길 원할 때 필요할 것이다.


이제 아래와 같이 CoolToolSet 를 로딩할 수 있다.

(ConfigurationOfCoolToolSet project version: '0.1') load.


비관습적 프로젝트

당신이 의존하는 프로젝트가 기본 관습을 따르지 않는다면 설정을 식별하기 위해 더 많은 정보를 제공해야 할 것이다. 설정이 만일 권장하는 ConfigurationOfCoolBrowser 대신 CoolBrowser-Metacello라는 Monticello 패키지에 보관된 ConfigurationOfCoolBrowser 클래스에 보관된다고 가정해보자.

ConfigurationOfCoolToolSet >>baseline01: spec
    <version: '0.1-baseline'>
    spec for: #common do: [
        spec repository: 'http://www.example.com/CoolToolSet'.
        spec project: 'CoolBrowser ALL' with: [
            spec
                className: 'ConfigurationOfCoolBrowser';
                loads: #('ALL' );
                file: 'CoolBrowser-Metacello';
                repository: 'http://www.example.com/CoolBrowser' ].
        spec
            package: 'CoolToolSet-Core' with: [ spec requires: 'CoolBrowser ALL' ];
            package: 'CoolToolSet-Tests' with: [ spec requires: 'CoolToolSet-Core' ]].


  • className: 는 프로젝트 메타데이터를 포함하는 클래스명을 명시하는데, 이번 경우 ConfigurationOfCoolBrowser가 되겠다.
  • file:와 repository: 메시지는 ConfigurationOfCoolBrowser가 이미지에 없을 경우 이를 검색하고 로딩해야 한다는 정보를 Metacello에게 제공한다. file: 의 인자는 메타데이터 클래스를 포함하는 Monticello 패키지의 이름이며, repository: 의 인자는 패키지를 포함하는 Monticello 저장소의 URL이다. Monticello 저장소가 보호된 경우 대신 repository:username:password: 메시지를 사용해야 한다.


이제 다음과 같이 CoolToolSet를 로딩할 수 있다.

(ConfigurationOfCoolToolSet project version: '0.1') load.


다수의 프로젝트에 의존하기

그림 9.8: 설정 간 의존성.

'ALL'을 이용하면 CoolToolSet-Core 이전에 전체 CoolBrowser 프로젝트가 로딩되는 결과를 야기할 것이다. CoolBrowser의 테스트 패키지 상의 의존성을 core 패키지 상의 의존성과 구별하여 명시하고 싶다면 아래 baseline을 정의해야 할 것이다.

ConfigurationOfCoolToolSet>>baseline02: spec
    <version: '0.2-baseline'>
    spec for: #common do: [
        spec blessing: #baseline.
        spec repository: 'http://www.example.com/CoolToolSet'.
        spec
            project: 'CoolBrowser default' with: [
                spec
                    className: 'ConfigurationOfCoolBrowser'; ''this is optional''
                    loads: #('default'); ''this is optional''
                    repository: 'http://www.example.com/CoolBrowser' ].
            project: 'CoolBrowser Tests' with: [
                spec
                    loads: #('Tests' );
                    repository: 'http://www.example.com/CoolBrowser' ].
        spec
            package: 'CoolToolSet-Core' with: [ spec requires: 'CoolBrowser default' ];
            package: 'CoolToolSet-Tests' with: [
                spec requires: #('CoolToolSet-Core' 'CoolBrowser Tests') ].].


해당 baseline은 두 개의 프로젝트 참조를 생성하는데, CoolBrowser default로 명명된 참조는 default 그룹을 로딩하고, 'CoolBrowser Tests'로 명명된 참조는 CoolBrowser의 설정의 'Tests' 그룹을 로딩한다. 우리는 CoolToolSet-Core가 CoolBrowser default를 필요로 하고 CoolToolSet-Tests가 CoolTestSet-Core와 CoolBrowser Tests를 필요로 하도록 선언한다. 의존적 프로젝트들의 컬렉션과 함께 requires: 의 사용도 주목한다.


이제 아래처럼 core 패키지만 로딩하거나,

(ConfigurationOfCoolToolSet project version: '0.2') load: 'CoolToolSet-Core'.


테스트만 로딩하는 것이 가능해졌다 (이 또한 core를 로딩할 것이다).

(ConfigurationOfCoolToolSet project version: '0.2') load: 'CoolToolSet-Tests'.


내적 의존성과 마찬가지로 baseline 0.2-baseline (그리고 0.1-baseline에서도 마찬가지로)은 설정이 의존하는 프로젝트 버전을 명시하지 않는다. 대신 버전 메서드에서 project:with: 메시지를 이용해 명시한다.

ConfigurationOfCoolToolSet>>version02: spec
    <version: '0.2' imports: #('0.2-baseline' )>
    spec for: #common do: [
        spec blessing: #beta.
        spec
            package: 'CoolToolSet-Core' with: 'CoolToolSet-Core-AlanJay.1';
            package: 'CoolToolSet-Tests' with: 'CoolToolSet-Tests-AlanJay.1';
            project: 'CoolBrowser default' with: '1.3';
            project: 'CoolBrowser Tests' with: '1.3'].


특정 패키지 로딩하기

버전 메서드는 baseline 메서드 외에도 어떤 패키지를 로딩할 것인지 명시할 수 있다. ConfigurationOfSoup를 예로 들어, 버전 1.2에 'XML-Parser'와 'XML-Tests-Parser' 패키지를 로딩하길 원한다고 가정하자.

ConfigurationOfSoup>>version10: spec
    <version: '1.0' imports: #('1.0-baseline')>

    spec for: #common do: [
        spec
            project: 'XMLSupport'
            with: [spec
                loads: #('XML-Parser' 'XML-Tests-Parser');
                versionString: '1.2.0'].

    spec
        package: 'Soup-Core' with: 'Soup-Core-sd.11';
        package: 'Soup-Tests-Core' with: 'Soup-Tests-Core-sd.3';
        package: 'Soup-Help' with: 'Soup-Help-StephaneDucasse.2' ].


당신이 할 수 있는 일은 로딩하고자 하는 프로젝트의 패키지를 명시하기 위해 프로젝트 참조에서 load: 메시지를 사용하는 것이다. 이는 당신이 프로젝트 참조에 정보를 팩토링하고 모든 버전에서 중복할 필요가 없기 때문에 바람직한 해결책이다.

ConfigurationOfSoup>>version10: spec
    <version: '1.0' imports: #('1.0-baseline')>

    spec for: #pharo do: [
        spec project: 'XMLSupport' with: [
            spec
                versionString: #stable;
                loads: #('XML-Parser' 'XML-Tests-Parser');
                repository: 'http://ss3.gemstone.com/ss/xmlsupport' ].

    spec
        package: 'Soup-Core' with: 'Soup-Core-sd.11';
        package: 'Soup-Tests-Core' with: 'Soup-Tests-Core-sd.3';
        package: 'Soup-Help' with: 'Soup-Help-StephaneDucasse.2' ].


Baseline 내 버전. 권장하진 않지만 baseline으로부터 버전을 명시하지 못하도록 당신을 막을 수 있는 방도는 없다. 프로젝트 참조도 마찬가지다. 따라서 file:, className:, repository: 등의 메시지 외에도 프로젝트 참조에 직접 프로젝트의 버전을 명시하도록 해주는 versionString: 이라는 메시지가 있는데, 예를 들자면 다음과 같다.

ConfigurationOfCoolToolSet >>baseline011: spec
    <version: '0.1.1-baseline'>
    spec for: #common do: [
        spec repository: 'http://www.example.com/CoolToolSet'.
        spec project: 'CoolBrowser ALL' with: [
            spec
                className: 'ConfigurationOfCoolBrowser';
                loads: #('ALL' );
                versionString: '0.6' ;
                file: 'CoolBrowser-Metacello';
                repository: 'http://www.example.com/CoolBrowser' ].
        spec
            package: 'CoolToolSet-Core' with: [ spec requires: 'CoolBrowser ALL' ];
            package: 'CoolToolSet-Tests' with: [ spec requires: 'CoolToolSet-Core' ]].


버전 메서드 내에 CoolBrowser default와 CoolBrowser Tests 참조에 대한 버전을 정의하지 않은 경우 baseline 내에 명시된 (versionString: 을 사용) 버전이 사용된다. baseline 메서드에 명시된 버전이 없다면 Metacello는 가장 최근 프로젝트 버전을 로딩한다.


정보 재사용하기. baseline02:을 보면 정보가 두 개의 프로젝트 참조에서 중복됨을 확인할 수 있다. 중복을 제거하기 위해서는 project:copyFrom:with: 메서드를 이용할 수 있다.

ConfigurationOfCoolToolSet >>baseline02: spec
    <version: '0.2-baseline'>
    spec for: #common do: [
        spec blessing: #baseline.
        spec repository: 'http://www.example.com/CoolToolSet'.
        spec
            project: 'CoolBrowser default' with: [
                spec
                    loads: #('default');
                    repository: 'http://www.example.com/CoolBrowser';
                    file: 'CoolBrowser-Metacello']
            project: 'CoolBrowser Tests'
                copyFrom: 'CoolBrowser default'
                with: [ spec loads: #('Tests').].
        spec
            package: 'CoolToolSet-Core' with: [ spec requires: 'CoolBrowser default' ];
            package: 'CoolToolSet-Tests' with: [
                spec requires: #('CoolToolSet-Core' 'CoolBrowser Tests') ].].


의존성 세분성(granularity)에 관하여

패키지에 의존하는 일, 프로젝트에 의존하는 일, 이를 표현하는 다른 방법들 간 차이를 논하고자 한다. Fame의 baseline1.1을 생각해보자.

baseline11: spec
    <version: '1.1-baseline'>
    spec for: #'common' do: [
        spec blessing: #'baseline'.
        spec description: 'Baseline 1.1 first version on SmalltalkHub, copied from baseline
        1.0 on SqueakSource'.
        spec repository: 'http://www.smalltalkhub.com/mc/Moose/Fame/main'.
        spec
            package: 'Fame-Core';
            package: 'Fame-Util';
            package: 'Fame-ImportExport' with: [spec requires: #('Fame-Core' ) ];
            package: 'Fame-SmalltalkBinding' with: [spec requires: #('Fame-Core' ) ];
            package: 'Fame-Example';
            package: 'Phexample' with: [spec repository: 'http://smalltalkhub.com/mc/
        PharoExtras/Phexample/main' ];
            package: 'Fame-Tests-Core' with: [spec requires: #('Fame-Core' 'Fame-
        Example' 'Phexample' ) ].
        spec
            group: 'Core' with: #('Fame-Core' 'Fame-ImportExport' 'Fame-Util' 'Fame-
        SmalltalkBinding' );
            group: 'Tests' with: #('Fame-Tests-Core' ) ].


baseline에서 package:'Phexample'with:[spec repository: 'http://smalltalkhub.com/mc/PharoExtras/Phexample/main' ]; 는 명시된 저장소에 하나의 패키지가 발견됨을 나타낸다. 하지만 그러한 접근법은 바람직하지 못한데, 이 방식대로 이용한다면 저장소로부터 패키지를 다운로드할 뿐, Metacello가 관계되지 않는다. 예를 들어, PhexampleCore가 의존성을 갖고 있더라도 로딩되지 않을 것이란 의미다.


아래 baseline1.2 예제에서 우리는 위에 제시한 프로젝트 간 의존성을 표현하겠다.

baseline12: spec
    <version: '1.2-baseline'>
    spec for: #'common' do: [
        spec blessing: #'baseline'.
        spec description: 'Baseline 1.2 to make explicit that Fame depends on HashTable
        and Phexample (now on smalltalkHub and with working configurations'.
        spec repository: 'http://www.smalltalkhub.com/mc/Moose/Fame/main'.
        spec project: 'HashTable' with: [
            spec
                versionString: #stable;
                repository: 'http://www.smalltalkhub.com/mc/Moose/HashTable/main' ].
        spec project: 'Phexample' with: [
            spec
                versionString: #stable;
                repository: 'http://www.smalltalkhub.com/mc/Phexample/main' ].
        spec
            package: 'Fame-Core' with: [spec requires: 'HashTable'];
            package: 'Fame-Util';
            package: 'Fame-ImportExport' with: [spec requires: #('Fame-Core' ) ];
            package: 'Fame-SmalltalkBinding' with: [spec requires: #('Fame-Core' ) ];
            package: 'Fame-Example';
            package: 'Fame-Tests-Core' with: [spec requires: #('Fame-Core' 'Fame-
         Example') ].
        spec
            group: 'Core' with: #('Fame-Core' 'Fame-ImportExport' 'Fame-Util' 'Fame-
        SmalltalkBinding' );
            group: 'Tests' with: #('Fame-Tests-Core' ) ].


이제 프로젝트 간 의존성을 표현하면 이것이 Phexample에 의존하는 Fame-Tests-Core 패키지이며 정보의 손실이라는 사실을 잊게 된다.


그러한 정보를 유지하기 위해 PhexampleCore 예제에서 명명한 새 프로젝트를 우리의 설정에서 정의하면 PhexampleCore로부터 Fame-Tests-Core가 아래와 같이 의존적임을 표현할 수 있을 것이다.

spec
    project: 'PhexampleCore'
    with: [ spec
        versionString: #stable;
        Executing code before and after installation 171
        loads: #('Core');
        repository: 'http://www.smalltalkhub.com/mc/Phexample/main' ].
    ....
    'Fame-Tests-Core' with: [spec requires: #('Fame-Core' 'Fame-Example' '
        PhexampleCore' ) ].


설치 전후에 코드 실행하기

때때로 패키지 또는 프로젝트가 로딩되기 전이나 후에 일부 코드를 실행할 필요가 있음을 발견할 것이다. 가령 System Browser를 설치하고 있다면 기본적으로 이를 로딩한 후에 등록하는 것이 좋을 것이다. 아니면 설치 이후 어느 정도 워크스페이스를 열기를 원할 수도 있다.


Metacello는 preLoadDoIt: 과 postLoadDoIt: 메시지를 이용해 이 기능을 제공한다. 이러한 메시지에 대한 인자는 아래에 표시된 설정 클래스 상에 정의된 메서드의 선택자이다. 현재로선 단일 패키지 또는 전체 프로젝트에 대해 pre-script와 post-script를 정의할 수 있다.


위의 예제를 계속하자면,

ConfigurationOfCoolBrowser>>version08: spec
    <version: '0.8' imports: #('0.7-baseline')>
    spec for: #common do: [
        spec
            package: 'CoolBrowser-Core' with: [
                spec
                    file: 'CoolBrowser-Core-BobJones.20';
                    preLoadDoIt: #preloadForCore;
                    postLoadDoIt: #postloadForCore:package: ];
                    ....
            package: 'CoolBrowser-AddonsTests' with: 'CoolBrowser-AddonsTests-
    JohnLewis.1' ].

ConfigurationOfCoolBrowser>>preloadForCore
    Transcript show: 'This is the preload script. Sorry I had no better idea'.

ConfigurationOfCoolBrowser>>postloadForCore: loader package: packageSpec
    Transcript cr;
        show: '#postloadForCore executed, Loader: ', loader printString,
             ' spec: ', packageSpec printString.
    Smalltalk at: #SystemBrowser ifPresent: [:cl | cl default: (Smalltalk classNamed:
        #CoolBrowser)].


눈치챘겠지만 preLoadDoit: 과 postLoadDoIt: 메서드는 로딩 전이나 후에 로딩될 선택자를 수신한다. postloadForCore:package: 메서드가 두 개의 매개변수를 취한다는 사실도 눈치챌 것이다. Pre/post load 메서드는 0, 1, 또는 2개 인자를 취할 수 있다. load는 첫 번째 선택적 인자이고, 로딩된 packageSpec은 두 번째 선택적 인자다. 자신의 필요에 따라 그러한 인자들 중 원하는 것으로 선택할 수 있다.


이러한 pre/post load 메서드 스크립트는 버전 메서드 뿐만 아니라 baseline에서도 사용할 수 있다. 스크립트가 버전에 의존하는 경우 버전에 넣어둘 수 있다. 여러 버전에 걸쳐 변경되지 않는다면 정확히 같은 방식으로 baseline 메서드에 넣어둘 수도 있다.


앞에서 언급하였듯 pre/post는 패키지 수준일 수도 있지만 프로젝트 수준에서 이루어질 수도 있다. 예를 들면 아래와 같은 설정을 가질 수 있다.

ConfigurationOfCoolBrowser>>version08: spec
    <version: '0.8' imports: #('0.7-baseline')>
    spec for: #common do: [
        spec blessing: #release.
        spec preLoadDoIt: #preLoadForCoolBrowser.
        spec postLoadDoIt: #postLoadForCoolBrowser.
        spec
            package: 'CoolBrowser-Core' with: [
                spec
                    file: 'CoolBrowser-Core-BobJones.20';
                    preLoadDoIt: #preloadForCore;
                    postLoadDoIt: #postloadForCore:package: ];
                package: 'CoolBrowser-Tests' with: 'CoolBrowser-Tests-JohnLewis.8';
                package: 'CoolBrowser-Addons' with: 'CoolBrowser-Addons-JohnLewis.6';
                package: 'CoolBrowser-AddonsTests' with: 'CoolBrowser-AddonsTests-
                    JohnLewis.1' ].


이 예제에서 우리는 프로젝트 수준에서 pre/post load 스크립트를 추가하였다. 다시 말하지만 선택자는 0, 1, 또는 2개의 인자를 수신 가능하다.


플랫폼 특정적인 패키지

설정이 로딩된 플랫폼에 따라 여러 패키지가 로딩되길 원한다고 가정해보자. Cool Browser 예제를 배경으로 할 때 CoolBrowser-Platform이라는 패키지를 가질 수 있다. 여기서 추상적 클래스, API 등을 정의할 수 있겠다. 그리고 CoolBrowser-PlatformPharo, CoolBrowser-PlatformGemstone 등의 패키지를 가질 수 있다.


Metacello는 사용된 플랫폼의 패키지를 자동으로 로딩한다. 하지만 이것이 가능하려면 아래 예제에서 보이는 바와 같이 for:do: 메서드를 이용해 플랫폼 특정적인 정보를 명시할 필요가 있다. 여기서 우리는 플랫폼에 따라 여러 패키지 버전이 로딩될 것이라고 정의한다. 당신이 스크립트를 실행하는 시스템에 따라 공통 패키지에 더해 플랫폼 특정적 패키지도 로딩될 것이다.

ConfigurationOfCoolBrowser>>version09: spec
    <version: '0.9' imports: #('0.9-baseline')>
    spec for: #common do: [
        ...
        spec
            ...
                package: 'CoolBrowser-AddonsTests' with: 'CoolBrowser-AddonsTests-
                    JohnLewis.1' ].
    spec for: #gemstone do: [
        spec package: 'CoolBrowser-Platform' with: 'CoolBrowser-PlatformGemstone-
            BobJones.4'.].
    spec for: #pharo do: [
        spec package: 'CoolBrowser-Platform' with: 'CoolBrowser-PlatformPharo-
            JohnLewis.7'.].


버전 뿐만 아니라 baseline 명세 정보도 명시해야 한다.

ConfigurationOfCoolBrowser>>baseline09: spec
    <version: '0.9-baseline'>
    spec for: #common do: [
        spec blessing: #baseline.
        spec repository: 'http://www.example.com/CoolBrowser'.
    spec
        package: 'CoolBrowser-Core';
        package: 'CoolBrowser-Tests' with: [ spec requires: 'CoolBrowser-Core' ];
        package: 'CoolBrowser-Addons' with: [ spec requires: 'CoolBrowser-Core' ];
        package: 'CoolBrowser-AddonsTests' with: [
            spec requires: #('CoolBrowser-Addons' 'CoolBrowser-Tests' ) ].
    spec
        group: 'default' with: #('CoolBrowser-Core' 'CoolBrowser-Addons' );
        group: 'Core' with: #('CoolBrowser-Core' 'CoolBrowser-Platform' );
        group: 'Extras' with: #('CoolBrowser-Addon');
        group: 'Tests' with: #('CoolBrowser-Tests' 'CoolBrowser-AddonsTests' );
        group: 'CompleteWithoutTests' with: #('Core', 'Extras' );
        group: 'CompleteWithTests' with: #('CompleteWithoutTests', 'Tests' )].
    spec for: #gemstone do: [
        spec package: 'CoolBrowser-Platform' with: 'CoolBrowser-PlatformGemstone'].
    spec for: #pharo do: [
        spec package: 'CoolBrowser-Platform' with: 'CoolBrowser-PlatformPharo'].


Core 그룹에 CoolBrowser-Platform 패캐지를 추가함을 주목하라. 이 패키지는 여느 것과 마찬가지로 획일적으로 관리함을 확인할 수 있다. 따라서 상당한 유연성을 가진다. 런타임에서 CoolBrowser를 로딩하면 Metacello는 로딩이 발생하는 dialect가 무엇인지 자동으로 감지하고 해당 dialect에 특정적인 패키지를 로딩한다. for:do:는 dialects 뿐만 아니라 그들의 구체적인 버전에도 적용되는데, 아래를 예로 들겠다.

ConfigurationOfCoolBrowser>>baseline09: spec
    <version: '0.9-baseline'>
    spec for: #common do: [
        spec blessing: #baseline.
        spec repository: 'http://www.example.com/CoolBrowser'.
        spec
            package: 'CoolBrowser-Core';
            package: 'CoolBrowser-Tests' with: [ spec requires: 'CoolBrowser-Core' ];
            package: 'CoolBrowser-Addons' with: [ spec requires: 'CoolBrowser-Core' ];
            package: 'CoolBrowser-AddonsTests' with: [
                spec requires: #('CoolBrowser-Addons' 'CoolBrowser-Tests' ) ].
        spec
            group: 'default' with: #('CoolBrowser-Core' 'CoolBrowser-Addons' );
            group: 'Core' with: #('CoolBrowser-Core' 'CoolBrowser-Platform' );
            group: 'Extras' with: #('CoolBrowser-Addon');
            group: 'Tests' with: #('CoolBrowser-Tests' 'CoolBrowser-AddonsTests' );
            group: 'CompleteWithoutTests' with: #('Core', 'Extras' );
            group: 'CompleteWithTests' with: #('CompleteWithoutTests', 'Tests' )].
    spec for: #gemstone do: [
        spec package: 'CoolBrowser-Platform' with: 'CoolBrowser-PlatformGemstone'].
    spec for: #pharo do: [
        spec package: 'CoolBrowser-Platform' with: 'CoolBrowser-PlatformPharo'].


로딩 순서. 당신이 만일 플랫폼 속성이 (#common #squeakCommon #pharo #'pharo2.x' #'pharo2.0.x') (이 정보는 ConfigurationOf project attributes를 실행해 얻을 수 있다) 위치한 시스템에 있고, #common, #pharo, #pharo2.0.x 와 같이 세 개의 section을 명시하였다면, 이러한 section들은 차례로 로딩될 것이다.

ConfigurationOfCoolBrowser>>baseline09: spec
    <version: '0.9-baseline'>
    spec for: #common do: [
    spec blessing: #baseline.
    spec repository: 'http://www.example.com/CoolBrowser'.
    spec
        package: 'CoolBrowser-Core';
        package: 'CoolBrowser-Tests' with: [ spec requires: 'CoolBrowser-Core' ];
        package: 'CoolBrowser-Addons' with: [ spec requires: 'CoolBrowser-Core' ];
        package: 'CoolBrowser-AddonsTests' with: [
            spec requires: #('CoolBrowser-Addons' 'CoolBrowser-Tests' ) ].
        spec
        group: 'default' with: #('CoolBrowser-Core' 'CoolBrowser-Addons' );
        group: 'Core' with: #('CoolBrowser-Core' 'CoolBrowser-Platform' );
        group: 'Extras' with: #('CoolBrowser-Addon');
        group: 'Tests' with: #('CoolBrowser-Tests' 'CoolBrowser-AddonsTests' );
        group: 'CompleteWithoutTests' with: #('Core', 'Extras' );
        group: 'CompleteWithTests' with: #('CompleteWithoutTests', 'Tests' )].
    spec for: #gemstone do: [
        spec package: 'CoolBrowser-Platform' with: 'CoolBrowser-PlatformGemstone'].
    spec for: #pharo do: [
        spec package: 'CoolBrowser-Platform' with: 'CoolBrowser-PlatformPharo'].
    spec for: #pharo2.0.x do: [
        spec package: 'CoolBrowser-Addons' with: 'CoolBrowser-Core20'].


마지막으로 for:do: 메서드는 플랫폼 특정적 패키지를 명시할 때 뿐만 아니라 여러 dialect와 관련된 것이라면 어디든 사용 가능함을 주목하라. 설정으로부터 원하는 것은 무엇이든 해당 블록에 넣을 수 있다. 그렇기 때문에 각 dialect마다 그룹, 패키지, 저장소 등을 정의, 변경, 맞춤설정할 수 있는 것이다. 아래를 일례로 들어보겠다.

ConfigurationOfCoolBrowser>>baseline010: spec
    <version: '0.10-baseline'>
    spec for: #common do: [
        spec blessing: #baseline.].
    spec for: #pharo do: [
        spec repository: 'http://www.pharo.com/CoolBrowser'.
        spec
            ...
        spec
            group: 'default' with: #('CoolBrowser-Core' 'CoolBrowser-Addons' );
            group: 'Core' with: #('CoolBrowser-Core' 'CoolBrowser-Platform' );
            group: 'Extras' with: #('CoolBrowser-Addon');
            group: 'Tests' with: #('CoolBrowser-Tests' 'CoolBrowser-AddonsTests' );
            group: 'CompleteWithoutTests' with: #('Core', 'Extras' );
            group: 'CompleteWithTests' with: #('CompleteWithoutTests', 'Tests' )].
    spec for: #gemstone do: [
        spec repository: 'http://www.gemstone.com/CoolBrowser'.
        spec
            package: 'CoolBrowser-Core';
            package: 'CoolBrowser-Tests' with: [ spec requires: 'CoolBrowser-Core' ];
        spec
            group: 'default' with: #('CoolBrowser-Core' 'CoolBrowser-Addons' );
            group: 'Core' with: #('CoolBrowser-Core' 'CoolBrowser-Platform' )].


이 예제에서 Pharo의 경우 Gemstone과 다른 저장소를 사용한다. 하지만 의무적인 것은 아닌데, 두 가지 모두 저장소는 같지만 버전, post/pre code 실행, 의존성 등에 차이가 있기 때문이다.


게다가 addons와 테스트는 Gemstone에서 이용할 수 있기 때문에 그러한 패키지와 그룹은 포함되지 않는다. 따라서 for:#common:do: 내부에서 우리가 한 일은 특정 dialect에 대한 다른 for:do: 내부에서도 실행할 수 있겠다.


중요한 개발: symbolic 버전

대규모로 발전하는 애플리케이션이라면 특정 버전과 함께 어떤 설정의 버전을 사용해야 할지 확인하기란 항상 까다롭다. ConfigurationOfOmniBrowser가 이런 문제의 일례를 보여주는데, Pharo1.0 one-click 이미지에선 1.1.3 버전이 사용되는데 버전 1.1.3은 Pharo1.2에서 로딩할 수 없고, 버전 1.1.5는 Pharo1.1용, 버전 1.2.3은 Pharo1.2용으로 Pharo1.0에선 로딩할 수 없으며, Pharo2.0에 이용할 수 있는 버전은 없다. Metacello를 이용해 무엇을 할 수 있는지 살펴보기 위해 이 예제로 설명하겠다.


Metacello는 기존 리터럴 버전(1.1.3, 1.1.5, 1.2.3)과 관련해 버전을 설명하기 위해 symbolic 버전의 개념을 도입한다. symbolic 버전은 symbolicVersion: pragma를 이용해 명시된다. 여기서는 Pharo의 버전마다 OmniBrowser 에 대한 안정된 버전을 정의하였다.

OmniBrowser>>stable: spec
    <symbolicVersion: #stable>
    spec for: #'pharo1.0.x' version: '1.1.3'.
    spec for: #'pharo1.1.x' version: '1.1.5'.
    spec for: #'pharo1.2.x' version: '1.2.3'.


symbolic 버전은 리터럴 버전을 사용할 수 있는 곳이라면 어디든 사용 가능하다. 아래와 같은 load 표현식부터,

(ConfigurationOfXMLParser project version: #stable) load
(ConfigurationOfXMLParser project version: #stable) load: 'Tests'


baseline 버전의 프로젝트 참조까지 가능하다.

baseline10: spec
    <version: '1.0-baseline'>
    spec for: #squeakCommon do: [
        spec blessing: #baseline.
        spec repository: 'http://seaside.gemstone.com/ss/GLASSClient'.
    spec
        project: 'OmniBrowser' with: [
        spec
            className: 'OmniBrowser';
            versionString: #stable;
            repository: 'http://www.squeaksource.com/MetacelloRepository' ].
    spec
        package: 'OB-SUnitGUI' with: [
            spec requires: #('OmniBrowser') ];
        package: 'GemTools-Client' with: [
            spec requires: #('OB-SUnitGUI') ];
        package: 'GemTools-Platform' with: [
            spec requires: #('GemTools-Client') ]].


여기서 #stable은 (어리석게도) baseline을 로딩하려 할 경우 얻게 될 최첨단 로딩(bleeding edge loading) 행위를 오버라이드함을 주목하라 (baseline을 로딩 시 최첨단 버전을 로딩함을 기억하라). 여기서 우리는 자신의 플랫폼에 대한 OmniBrowser의 (최신 버전이 아니라) 안정적 버전이 확실히 로딩되도록 한다. 다음 절에서는 이와 다른 symbolic 버전을 다루겠다.


표준 symbolic 버전

몇 가지 표준 symbolic 버전이 정의된다.


stable. 특정 플랫폼과 그러한 플랫폼의 버전에 대한 안정적인 리터럴 버전을 명시하는 symbolic 버전. 안정적 버전은 로딩에 사용되어야 하는 버전이다. bleedingEdge 버전을 제외하고 (기본적으로 사전 정의된 버전) stable 또는 development 버전 정보를 추가하도록 자신의 설정을 편집해야 할 것이다. 나는 플랫폼에 승인된 버전을 원한다. 패키지의 안정적 버전에 의존할 경우 패키지가 변경될 수 없다는 의미는 아니기 때문에 주의를 기울여야 할 때다. 사실 패키지 구현자가 자신의 시스템과 호환이 되지 않는 새 버전을 생성할 수도 있다.

development. 개발 시 사용할 리터럴 버전을 명시하는 symbolic 버전 (예: development를 사용 시). 일반적으로 development 버전은 개발자들이 bleedingEdge에서 stable로 프로젝트를 전환하는 등의 배포 전(pre-release) 활동을 관리하기 위해 개발자들에 의해 사용된다. 나는 플랫폼에 승인된 버전을 원하지만 개발 모드였으면 좋겠다 라는 의미다.

bleedingEdge. 최신 mcz 파일과 프로젝트 버전을 명시하는 symbolic 버전. 기본적으로 bleedingEdge symbolic 버전은 이용 가능한 최신 baseline 버전으로서 정의된다. bleedingEdge에 대한 기본 명세는 모든 프로젝트에 대해 정의된다. bleedingEdge 버전은 주로 자신들이 어떤 일을 하는지 아는 개발자들을 위한 것이다. 올바로 bleedingEdge 버전이 기능하는지는 제쳐두고 그것이 로딩될 것인지도 보장할 수 없다. 나는 가장 최근에 공개된 파일을 원한다.


symbolicVersion: pragma 의 형식으로 symbolic 버전을 명시할 때는 symbolic 버전의 stable에 대한 아래의 정의처럼 또 다른 symbolic 버전을 사용해도 좋다.

stable: spec
    <symbolicVersion: #stable>
    spec for: #gemstone version: '1.5'.
    spec for: #'squeak' version: '1.4'.
    spec for: #'pharo1.0.x' version: '1.5'.
    spec for: #'pharo1.1.x' version: '1.5'.
    spec for: #'pharo1.2.x' version: #development.


아니면 development symbolic 버전의 정의에서처럼 특수 symbolic 버전 notDefined: 를 사용하는 방법도 있겠다.

development: spec
    <symbolicVersion: #development>
    spec for: #common version: #notDefined.
    spec for: #'pharo1.1.x' version: '1.6'.
    spec for: #'pharo1.2.x' version: '1.6'.


여기서는 common 태그에 대한 버전이 없다고 나타난다. notDefined로 해결되는 symbolic 버전을 이용 시 MetacelloSymbolicVersionNotDefinedError가 시그널링된다.


개발 symbolic 버전의 경우 원하는 버전은 무엇이든 사용할 수 있다 (다른 symbolic 버전도 포함). 아래 코드가 보여주듯이 특정 버전, baseline(baseline이 명시한 최신 버전을 로딩시킬), 또는 안정적 버전을 명시할 수 있다.

development: spec
    <symbolicVersion: #'development'>
    spec for: #'common' version: '1.1'
development: spec
    <symbolicVersion: #'development'>
    spec for: #'common' version: '1.1-baseline'
development: spec
    <symbolicVersion: #'development'>
    spec for: #'common' version: #stable


경고. 안정적이란 말은 오해를 불러일으키기 쉬운 용어이므로 주의를 기울일 것을 다시 요청한다. 당신이 의존하는 시스템의 개발 시 다른 안정적 버전을 가리키도록 '안정적'이란 의미를 변경할 수 있기 때문에 당신이 정확히 같은 버전을 항상 로딩하게 될 것이란 뜻은 아니며, 그러한 안정적 버전이 당신의 코드와 호환되지 않을 가능성이 있다는 의미다. 따라서 자신의 코드를 배포할 때는 다른 변경 내용의 영향을 받지 않도록 특정 버전을 사용해야 한다.


프로젝트 Blessing과 로딩

패키지 또는 프로젝트는 가령 개발, 알파, 베타, 릴리즈와 같은 라이프 사이클이나 소프트웨어 개발 과정 동안에 여러 단계를 거친다. 때로는 프로젝트의 상태를 칭하고 싶을 때가 있다.


blessing은 load logic에 의해 고려된다. 아래 표현식의 결과가

ConfigurationOfCoolBrowser project latestVersion.


이렇듯이 항상 마지막 버전이 되는것은 아니다. 그 이유는 latestVersion은 blessing이 #development, #broke, #blessing에 해당하지 않는 최신 버전을 응답하기 때문이다. 예를 들어, 최신 #development 버전을 찾기 위해서는 아래 표현식을 실행해야 한다.

ConfigurationOfCoolBrowser project latestVersion: #development.


그럼에도 불구하고 아래와 같이 lastVersion을 이용해 blessing과 상관없이 마지막 버전을 얻을 수도 있다.

ConfigurationOfCoolBrowser project lastVersion.


일반적으로 불안정한 버전에는 #development blessing를 사용해야 한다. 버전이 안정화되고 나면 다른 blessing을 적용시켜야 한다.


아래 표현식은 최신 #baseline 버전에 대한 모든 패키지의 최신 버전을 로딩할 것이다.

(ConfigurationOfCoolBrowser project latestVersion: #baseline) load.


최신 #baseline 버전은 가장 최신의 프로젝트 구조를 반영해야 하기 때문에 앞의 표현식을 이용하면 프로젝트의 완전한 bleeding edge 버전을 로딩한다.


힌트

일부 패턴은 Metacello와 작업할 때 생겨나기도 한다. 한 가지 예를 들어보겠다. Baseline 버전을 생성하고 baseline 내 모든 프로젝트에 대해 #stable 버전을 사용하라. 리터럴 버전에서 명시적 버전을 이용하면, 함께 작업하는 것으로 알려진 프로젝트 집합에 대해 명시적인 반복 가능 명세를 얻을 수 있다.


예로, pharo 1.2.2-baseline는 아래와 같은 명세를 포함할 것이다.

spec
    project: 'OB Dev' with: [
        spec
            className: 'ConfigurationOfOmniBrowser';
            versionString: #stable;
            ...];
    project: 'ScriptManager' with: [
        spec
            className: 'ConfigurationOfScriptManager';
            versionString: #stable;
            ...];
    project: 'Shout' with: [
        spec
            className: 'ConfigurationOfShout';
            versionString: #stable;
            ...];
        ....].


Pharo 1.2.2-baseline를 로딩 시 로딩되어야 할 프로젝트마다 #stable 버전을 야기할 것이지만...시간이 지나면서 #stable 버전은 변경되고, 패키지들 간 비호환성이 발견되기 시작함을 기억하라. #stable 버전을 이용하면 #bleedingEdge를 이용할 때보다 더 나은 결과를 야기할 것인데, #stable 버전은 작동하는 것으로 알려져 있기 때문이다.


Pharo 1.2.2(리터럴 버전)은 아래와 같은 명세를 가질 것이다.

spec
    project: 'OB Dev' with: '1.2.4';
    project: 'ScriptManager' with: '1.2';
    project: 'Shout' with: '1.2.2';
    ....].


이제 이러한 버전들은 서로 작업하는 것으로 알려져 있음을 확신할 수 있을 것이다 (단체로 테스트를 합격한 셈이다). 향후 5년 간은 Pharo 1.2.2를 로딩하면서 매번 정확히 같은 패키지를 얻을 것이지만, #stable 버전은 시간이 지나면 없어질지도 모른다.


이제 막 PharoCore1.2 이미지를 가져와 Pharo dev 코드를 로딩하고자 한다면 Pharo의 #stable 버전을 로딩해야 한다 (어쩌면 오늘은 1.2.2를, 내일은 1.2.3을). 누군가 작업 중인 환경을 중복하고자 한다면 그들에게 Pharo의 버전을 묻고 나서 버그와 같은 것들을 복제하기 위해 명시적 버전을 로딩해야 한다.


패키지 구조 변경은 어떻게 처리하는가?

Pharo13과 Pharo14에 애플리케이션을 생성하고자 하는데 애플리케이션을 변경했거나 패키지가 베이스 시스템으로 통합되었다는 이유로 애플리케이션이 버전 하나 당 패키지를 하나만 갖고 있다고 가정해보자.


이에 대한 해결책은 아래와 같이 의존성을 정의하고 symbolic 태그를 마커(marker)로서 사용하는 것이다.

spec for: #'pharo' do: [
    spec package: 'that depends upon zinc' with: [
        "the common required packages for your package"
    ].

spec for: #'pharo1.3.x' do: [
    spec project: 'Zinc' with: [
        spec
            className: 'ConfigurationOfZinc';
            versionString: #'stable';
            repository: 'http://www.squeaksource.com/MetacelloRepository' ].
    spec package: 'that depends upon zinc' with: [
        spec requires: #('Zinc') ].
].


자신의 baseline에 stable 버전을 사용할 경우 자신의 버전 명세에 특별한 일을 할 필요가 없다.


로드 타입

Metacello는 자체의 "로드 타입(load type)"을 통해 패키지가 로딩되는 방식을 명시하도록 해준다. 이 문서를 작성하는 시점에선 두 가지의 로드 타입, atomic과 linear가 가능하다.


Atomic 로딩은 패키지들이 따로 로딩될 수 없도록 분할(partitioned)되었을 때 사용된다. 각 패키지로부터 정의는 Monticello 패키지 로더에 의해 하나의 거대한 로드로 모인다. 클래스 측 initialize 메서드와 pre/post 코드 실행은 개별적 패키지가 아니라 패키지 전체 집합을 대상으로 실행된다.


Linear 로드를 사용할 경우 각 패키지는 순서대로 로딩된다. 클래스 측 initialize 메서드와 pre/post 코드 실행은 특정 패키지가 로딩된 전 또는 후에 실행된다.


의존성을 관리한다고 해서 순서대로 패키지가 로딩될 것을 의미하지는 않음을 인식하는 것이 중요하다. 패키지 A가 패키지 B를 의존한다고 해서 B가 A보다 먼저 로딩될 것이란 의미는 아니다. 그저 A를 로딩한 후에 B를 로딩하길 원할 경우 그렇게 진행되도록 보장할 뿐이다.


이와 관련된 문제는 메서드 오버라이드와 관련해 발생한다. 패키지가 다른 패키지의 메서드를 오버라이드할 경우 순서는 보존되지 않는데, 로딩될 순서를 확신할 수 없고 그에 따라 메서드의 어떤 버전이 결국 로딩될 것인지 확신할 수 없기 때문에 문제가 된다.


Atomic 로딩을 이용하면 패키지 순서가 손실되고 앞서 언급한 문제도 발생한다. 하지만 linear 모드를 이용하면 각 패키지가 순서대로 로딩된다. 뿐만 아니라 메서드 오버라이드도 보존된다.


Lienar 모드에서 발생 가능한 문제를 설명하기 위해 가령 프로젝트 A가 두 개의 프로젝트, B와 C에 의존한다고 가정해보자. B는 프로젝트 D 버전 1.1을 의존하고, C는 프로젝트 D 버전 1.2(동일한 프로젝트의 다른 버전)에 의존한다. 첫 번째 질문은, 'A는 결국 D의 어떤 버전을 갖게 될까'가 된다. 기본적으로(이는 project 메서드에서 operator: 메서드를 이용해 변경 가능하다) Metacello는 최신 버전, 즉 버전 1.2를 로딩할 것이다.


하지만 로드 타입과 관련해, atomic 로딩에서는 1.2 버전만 로딩된다. Linear 로딩에서는 두 가지 버전 모두 (의존성 순서에 따라) 로딩이 가능하지만 1.2가 결국 로딩될 것이다. 다시 말해 1.1 버전이 먼저 로딩된 다음 1.2가 로딩될지 모른다는 의미다. 이조차도 문제가 되기도 하는데, 오래된 버전의 패키지나 프로젝트가 우리가 사용 중인 Pharo 이미지에서 로딩되지 않을 수 있기 때문이다.


이러한 연유로 하여 linear가 기본 모드가 된다. 사용자는 특별한 경우에, 그리고 전적으로 확신할 때 atomic 로딩을 사용해야 한다.


마지막으로 명시적으로 로드 타입을 설정하길 원한다면 project 메서드를 통해야 할 것인데, 아래에 예를 들어보겠다.

ConfigurationOfCoolToolSet >>project
    ^ project ifNil: [ | constructor |
        "Bootstrap Metacello if it is not already loaded"
        self class ensureMetacello.
        "Construct Metacello project"
        constructor := (Smalltalk at: #MetacelloVersionConstructor) on: self.
        project := constructor project.
        project loadType: #linear. '"or #atomic"'
        project ]


조건부 로딩

사용자는 프로젝트를 로딩 시 주로 특정 상태, 가령 이미지에 다른 특정 패키지의 존재 유무에 따라 특정 패키지를 로딩할 것인지 말 것인지 결정하길 원한다. 자신의 이미지에 Seaside를 로딩하고 싶다고 치자. Seaside는 OmniBrowser에 의존하는 툴을 갖고 있으며, 웹 서버의 인스턴스를 관리하는 데에 사용된다. 이렇게 작은 툴로 할 수 있는 일은 코드를 이용해 실행할 수도 있다. 그러한 툴을 로딩하기 위해선 OmniBrowser가 필요하다. 하지만 다른 사용자들에겐 해당 패키지가 필요하지 않을지도 모른다. 대안적 방법으로 여러 그룹, 즉 그러한 패키지를 포함하는 그룹과 포함하지 않은 그룹을 제공하는 방법이 있다. 문제는 최종 사용자가 이를 인지하여 여러 상황에서 여러 그룹을 로딩해야 한다는 데에 있다. 조건부 로딩을 이용하면 OmniBrowser가 이미지에 있을 때에만 Seaside 툴을 로딩할 수 있다. 이는 Metacello가 자동으로 실행할 것이므로 특정 그룹을 명시적으로 로딩할 필요가 없다.


우리의 CoolToolSet가 더 많은 기능을 제공하기 시작한다고 가정하자. 먼저 core를 두 개의 패키지, 'CoolToolSet-Core'와 'coolToolSet-CB'로 나눈다. CoolBrowser는 한 이미지에만 존재가 가능하며 다른 이미지에선 존재해선 안 된다. 우리는 CoolBrowser가 있을 경우에만 기본 값으로 'CoolToolSet-CB' 패키지를 로딩하길 원한다.


Metacello에서 앞서 언급한 조건문(conditionals)은 앞 절에서 살펴본 프로젝트 속성을 이용해 확보할 수 있다. 이러한 조건문은 project 메서드에 정의되는데, 아래를 예로 들어보겠다.

ConfigurationOfCoolBrowser >>project
    | |
    ^ project ifNil: [ | constructor |
        "Bootstrap Metacello if it is not already loaded"
        self class ensureMetacello.
        "Construct Metacello project"
        constructor := (Smalltalk at: #MetacelloVersionConstructor) on: self.
        project := constructor project.
        projectAttributes := ((Smalltalk at: #CBNode ifAbsent: []) == nil
            ifTrue: [ #( #'CBNotPresent' ) ]
            ifFalse: [ #( #'CBPresent' ) ]).
        project projectAttributes: projectAttributes.
        project loadType: #linear.
        project ]


코드에서 볼 수 있듯이 우리는 CBNode 클래스(CoolBrowser의 클래스)가 존재하는지, 그리고 우리가 설정한 특정 프로젝트 속성에 의존하는지 확인한다. 자신만의 조건문을 정의하고 자신이 원하는 프로젝트 속성 수량을 설정하도록 (속성의 배열을 정의할 수 있다) 허용할 정도로 유연하다. 이제 문제는 이러한 프로젝트 속성을 어떻게 사용하느냐가 된다. 아래 baseline에서 예제를 살펴보겠다.

ConfigurationOfCoolToolSet >>baseline02: spec
    <version: '0.2-baseline'>
    spec for: #common do: [
    spec blessing: #baseline.
    spec repository: 'http://www.example.com/CoolToolSet'.
    spec project: 'CoolBrowser default' with: [
        spec
            className: 'ConfigurationOfCoolBrowser';
            versionString: '1.0';
            loads: #('default' );
            file: 'CoolBrowser-Metacello';
            repository: 'http://www.example.com/CoolBrowser' ];
        project: 'CoolBrowser Tests'
            copyFrom: 'CoolBrowser default'
            with: [ spec loads: #('Tests').].
    spec
        package: 'CoolToolSet-Core';
        package: 'CoolToolSet-Tests' with: [
            spec requires: #('CoolToolSet-Core') ];
        package: 'CoolToolSet-CB';

    spec for: #CBPresent do: [
        spec
            group: 'default' with: #('CoolToolSet-CB' )
            yourself ].

    spec for: #CBNotPresent do: [
        spec
            package: 'CoolToolSet-CB' with: [ spec requires: 'CoolBrowser default'];
                yourself ].
        ].


프로젝트 속성은 기존 메서드 for:do: 를 통해 사용됨을 눈치챘을 것이다. 해당 메서드 내에서는 그룹, 의존성 등을 정의하는 등 당신이 원하는 대로 할 수 있다. 우리 예제의 경우는, CoolBrowser가 존재한다면 기본 그룹으로 'CoolToolSet-CB'를 추가하기만 하면 된다. CoolBrowser가 없다면 'CoolBrowser default'가 'CoolToolSet-CB'에 대한 의존성에 추가된다. 이번 사례에서 우리는 기본 그룹으로 추가하길 원치 않으므로 삼가한다. 사용자가 원한다면 해당 패키지도 명시적으로 로딩할 수 있겠다.


다시 말하지만 for:do: 안에서 원하는 것을 마음껏 실행할 수 있음을 주목하라.


프로젝트 버전 속성

설정에는 여러 개의 선택적 속성이 있는데, 작성자, 설명, blessing, 타임스탬프를 들 수 있겠다. 우리 프로젝트의 새 0.7 버전으로 예를 들어보겠다.

ConfigurationOfCoolBrowser>>version07: spec
    <version: '0.7' imports: #('0.7-baseline')>
    spec for: #common do: [
        spec blessing: #release.
        spec description: 'In this release...'.
        spec author: 'JohnLewis'.
        spec timestamp: '10/12/2009 09:26'.
        spec
            package: 'CoolBrowser-Core' with: 'CoolBrowser-Core-BobJones.20';
            package: 'CoolBrowser-Tests' with: 'CoolBrowser-Tests-JohnLewis.8';
            package: 'CoolBrowser-Addons' with: 'CoolBrowser-Addons-JohnLewis.6';
            package: 'CoolBrowser-AddonsTests' with: 'CoolBrowser-AddonsTests-JohnLewis.1'
        ].


각 속성을 상세히 설명해보겠다.

Description(설명): 버전에 대한 텍스트 설명. 버그 수정이나 새 기능 리스트, changelog 등을 포함할 수 있다.

Author(작성자): 버전을 생성한 작성자의 이름. OB-Metacello 툴이나 MetacelloToolbox를 사용할 시 작성자 필드는 이미지에 정의된 현재 작성자를 반영하도록 자동으로 업데이트된다.

TimeStamp: 버전이 완성된 당시의 날짜와 시간. OB-Metacello 툴이나 MetacelloToolbox를 사용 시 타임스탬프 필드는 현재 날짜와 시간을 반영하도록 자동으로 업데이트 된다. 타임스태프는 String이어야 한다는 사실을 주목한다.


이번 장을 마무리하기 위해 해당 정보를 질의하는 방법을 보여주고자 한다. 이는 Metacello 버전에 정의한 정보 대부분을 질의할 수 있음을 보여준다. 예를 들어서 아래 표현식을 평가할 수 있겠다.

(ConfigurationOfCoolBrowser project version: '0.7') blessing.
(ConfigurationOfCoolBrowser project version: '0.7') description.
(ConfigurationOfCoolBrowser project version: '0.7') author.
(ConfigurationOfCoolBrowser project version: '0.7') timestamp.


요약

Metacello는 Pharo에서 중요한 부분이다. 이는 당신의 프로젝트를 조정하도록 해준다. 또한 새 버전으로 그리고 어떤 패키지를 위해 이동하길 원할 때를 제어하도록 해준다. 이는 중요한 아키텍처 뼈대이다.

Metacello Memento

ConfigurationOfCoolToolSet>>baseline06: spec "could be called differently just a convention"
    <version: '0.6-baseline'> "Convention. Used in the version: method"
    spec for: #common do: [ "#common/#pharo/#gemstone/#pharo'1.4'"
        spec blessing: #baseline. "Important: identifies a baseline"
        spec repository: 'http://www.example.com/CoolToolSet'.

        "When we depend on other projects"
        spec project: 'CoolBrowser default' with: [
            spec
                className: 'ConfigurationOfCoolBrowser'; "Optional if convention followed"
                versionString: #bleedingEdge; "Optional. Could be #stable/#bleedingEdge/specific version"
                loads: #('default'); "which packages or groups to load"
                file: 'CoolBrowser-Metacello'; "Optional when same as class name"
                repository: 'http://www.example.com/CoolBrowser' ];
            project: 'CoolBrowser Tests'
                copyFrom: 'CoolBrowser default' "Just to reuse information"
                with: [ spec loads: #('Tests').]. "Just to reuse information"

        "Our internal package dependencies"
        spec
            package: 'CoolToolSet-Core';
            package: 'CoolToolSet-Tests' with: [ spec requires: #('CoolToolSet-Core') ];
            package: 'CoolBrowser-Addons' with: [ spec requires: 'CoolBrowser-Core' ] ;
            package: 'CoolBrowser-AddonsTests' with: [
                spec requires: #('CoolBrowser-Addons' 'CoolBrowser-Tests' ) ].

        spec
            group: 'default' with: #('CoolBrowser-Core' 'CoolBrowser-Addons');
            group: 'Core' with: #('CoolBrowser-Core');
            group: 'Extras' with: #('CoolBrowser-Addon');
            group: 'Tests' with: #('CoolBrowser-Tests' 'CoolBrowser-AddonsTests');
            group: 'CompleteWithoutTests' with: #('Core' 'Extras');
            group: 'CompleteWithTests' with: #('CompleteWithoutTests' 'Tests')
    ].
ConfigurationOfCoolBrowser>>version07: spec "could be called differently just a convention"
    <version: '0.7' imports: #('0.6-baseline')> "Convention. No baseline so this is version"
                                                "do not import baseline from other baselines"
    spec for: #common do: [ "#common/#pharo/#gemstone/#pharo'1.4'"
        spec blessing: #release. "Required #development/#release: release means that it will not change
            anymore"
        spec description: 'In this release .....'.
        spec author: 'JohnLewis'.
        spec timestamp: '10/12/2009 09:26'.
        spec
            package: 'CoolBrowser-Core' with: 'CoolBrowser-Core-BobJones.20';
            package: 'CoolBrowser-Tests' with: 'CoolBrowser-Tests-JohnLewis.8';
            package: 'CoolBrowser-Addons' with: 'CoolBrowser-Addons-JohnLewis.6' ;
            package: 'CoolBrowser-AddonsTests' with: 'CoolBrowser-AddonsTests-JohnLewis.1']
ConfigurationOfGemToolsExample>>development: spec "note that the selector can be anything"
    <symbolicVersion: #development> "#stable/#development/#bleedingEdge"
    spec for: #common version: '1.0'. "'1.0' is the version of your development version"
    "#common or your platform attributes: #gemstone, #pharo, or #'pharo1.4'"
ConfigurationOfGemToolsExample>>baseline10: spec
    <version: '1.0-baseline'>
    spec for: #common do: [
        spec blessing: #'baseline'. "required see above"
        spec repository: 'http://seaside.gemstone.com/ss/GLASSClient'.
        spec
            project: 'FFI' with: [
                spec
                    className: 'ConfigurationOfFFI';
                    versionString: #bleedingEdge; "Optional. #stable/#development/#bleedingEdge/specificversion"
                    repository: 'http://www.squeaksource.com/MetacelloRepository' ];
            project: 'OmniBrowser' with: [
                spec
                    className: 'ConfigurationOfOmniBrowser';
                    versionString: #stable; "Optional. #stable/#development/#bleedingEdge/specificversion"
                    repository: 'http://www.squeaksource.com/MetacelloRepository' ];
            project: 'Shout' with: [
                spec
                    className: 'ConfigurationOfShout';
                    versionString: #stable;
                    repository: 'http://www.squeaksource.com/MetacelloRepository' ];
            project: 'HelpSystem' with: [
                spec
                    className: 'ConfigurationOfHelpSystem';
                    versionString: #stable;
                    repository: 'http://www.squeaksource.com/MetacelloRepository'].
        spec
            package: 'OB-SUnitGUI' with: [spec requires: #('OmniBrowser')];
            package: 'GemTools-Client' with: [ spec requires: #('OmniBrowser' 'FFI' 'Shout' 'OB-SUnitGUI' ).];
            package: 'GemTools-Platform' with: [ spec requires: #('GemTools-Client' ). ];
            package: 'GemTools-Help' with: [
        spec requires: #('HelpSystem' 'GemTools-Client' ). ].
            spec group: 'default' with: #('OB-SUnitGUI' 'GemTools-Client' 'GemTools-Platform' 'GemTools-Help')].
ConfigurationOfGemToolsExample>>version10: spec
    <version: '1.0' imports: #('1.0-baseline' )>
    spec for: #common do: [
        spec blessing: #development.
        spec description: 'initial development version'.
        spec author: 'dkh'.
        spec timestamp: '1/12/2011 12:29'.
    spec
        project: 'FFI' with: '1.2';
        project: 'OmniBrowser' with: #stable;
        project: 'Shout' with: #stable;
        project: 'HelpSystem' with: #stable.
    spec
        package: 'OB-SUnitGUI' with: 'OB-SUnitGUI-dkh.52';
        package: 'GemTools-Client' with: 'GemTools-Client-NorbertHartl.544';
        package: 'GemTools-Platform' with: 'GemTools-Platform.pharo10beta-dkh.5';
        package: 'GemTools-Help' with: 'GemTools-Help-DaleHenrichs.24'. ].


Loading. load, load:. load 메서드는 기본 그룹을 로딩하는데, 기본 그룹이 정의되지 않은 경우 모든 패키지가 로딩된다. load: 메서드는 패캐지명, 프로젝트명, 그룹명, 또는 그러한 항목들의 컬렉션명을 매개변수로 취한다.

(ConfigurationOfCoolBrowser project version: '0.1') load.
(ConfigurationOfCoolBrowser project version: '0.2') load: {'CBrowser-Core' . 'CBrowserAddons'}.


Debugging(디버깅). Record, record: loadDirectives. record 메시지는 기본 그룹을 기록하는데, 특정 항목의 그룹을 원할 경우 load처럼 record: 를 사용하면 된다.

((ConfigurationOfCoolBrowser project version: '0.2') record:
    { 'CoolBrowser-Core' .
    'CoolBrowser-Addons' }) loadDirective.


모든 파일 버전으로 재귀적으로 접근하고자 한다면 아래 예제와 같이 packageDirectives: 를 사용하라.

| pkgs loader |
loader := ((Smalltalk globals at: #ConfigurationOfMoose) project version: 'default')
    ignoreImage: true;
    record.

pkgs := OrderedCollection new.
loader loadDirective packageDirectivesDo: [:directive |pkgs add: directive spec file ].
pkgs.


권장하는 개발 과정. metacello를 이용해 아래와 같은 개발 단계를 권한다.

Baseline 			"first we define a baseline"
Version development 		"Then a version tagged as development"
Validate the map 		"Once it is validated and the project status arrives to the desired status"
Version release 		"We are ready to tag a version as release"
Version development 		"Since development continue we create a new version"
... 					"Tagged as development. It will be tagged as release and so on"
Baseline 				"When architecture or structure changes, a new baseline will appear"
Version development 			"and the story will continue"
Version release


Notes