토파즈 배우기

첫 애플리케이션

의존성 잠금과 테스트를 포함한 2개 모듈 패키지로 학습 계획을 작성합니다. 토파즈 소스 코드 및 컴파일러 없이 Python 아티팩트를 실행합니다.

학습 결과: 의존성이 잠긴 2개 모듈 패키지를 구성합니다. 지정한 테스트와 패키지 진입점을 실행합니다. 이어서 .tpz 프로젝트와 토파즈 컴파일러 없이 Python 아티팩트를 실행합니다.

선행 학습: 실패와 리소스 과정을 완료해야 합니다. topaz 및 Python 3.11 이상 환경이 설치되어 있어야 합니다.

앞선 다섯 개 수업에서는 단일 소스 파일만 사용하여 언어 기능과 리소스 처리를 확인했습니다. 이번 수업은 전체 학습 경로의 마지막이자 가장 큰 단계로, 소스 파일을 패키지로 묶고 테스트와 독립 아티팩트 빌드까지 완성하는 과정을 다룹니다.

기본 스캐폴드로 시작하기

프로젝트를 생성할 디렉터리에서 다음 명령을 실행합니다.

BASH
topaz init --root study-plan
cd study-plan

topaz init --root study-plan 명령은 프로젝트 구조를 새로 만드는 첫 단계입니다. 이 명령은 작업 디렉터리에 study-plan 폴더를 생성하고 패키지 설정 파일인 topaz.toml과 기본 진입점 소스 src/main.tpz를 함께 만들어 냅니다. 이어서 실행하는 cd study-plan 명령은 새로 만들어진 프로젝트 디렉터리로 들어가 이후 수행할 패키지 명령들의 기준 경로를 맞추는 역할을 합니다.

init 명령은 기존 프로젝트를 덮어쓰지 않고 topaz.tomlsrc/main.tpz를 생성합니다. 록파일과 테스트는 생성되지 않습니다. 진입점 소스 코드를 수정하고 모듈 및 지정한 테스트를 추가해 다음 구조를 완성하세요.

study-plan/
├── topaz.toml
├── src/
│   ├── main.tpz
│   └── plan.tpz
└── tests/
    └── plan.tpz

topaz.toml이 처음 등장하는 이 대목에서 매니페스트 파일의 구체적 역할을 확인하세요. topaz.toml은 패키지의 이름, 버전, 언어 버전, 진입점 파일, 빌드 타깃, 그리고 외부 의존성을 선언하는 파일입니다. 앞선 다섯 수업은 단일 소스 파일 하나만 구동했기 때문에 여러 모듈 간 연결이나 의존성 및 빌드 타깃을 명시할 이유가 없었고 매니페스트 파일도 쓸 필요가 없었습니다. 하지만 패키지 구조로 복수의 모듈을 작성하고 의존성을 관리하며 빌드 아티팩트를 출력하는 이번 단계를 진행하려면 프로젝트 설정의 기준점이 되는 topaz.toml이 필요해집니다.

스캐폴드가 생성한 매니페스트 파일은 다음과 같습니다.

TOML
[package]
name = "study-plan"
version = "0.1.0"
language = "5.19"
entry = "src/main.tpz"

[build]
target = "native"
deterministic = true

[dependencies]
std = "5.19"

이 매니페스트는 [package]에서 패키지 이름 "study-plan", 버전 "0.1.0", 언어 버전 "5.19", 엔트리 포인트 src/main.tpz를 지정하고, [build]로 빌드 타깃을, [dependencies]로 표준 라이브러리 요구사항 "5.19"를 선언합니다.

모듈 두 개 만들기

단일 소스 파일에 모든 로직을 모아두면 코드가 커질수록 관리가 어려워집니다. 도메인 모델 및 연산 로직과 실제 실행 진입점을 분리하기 위해 프로젝트 소스를 두 개의 모듈로 나누어 작성합니다.

src/plan.tpz 파일에는 도메인 모델과 재사용할 연산을 정의합니다.

TOPAZ
export record 학습과제 {
    제목: string,
: int,
    완료: bool = false,
}

function 분해석(텍스트: string) -> Result<int, string> {
    match toInt(텍스트) {
        case Some() if > 0 => Ok()
        case _ => Err("minutes must be a positive integer")
    }
}

export function 과제만들기(
    제목: string,
    분문자열: string,
    완료: bool = false,
) -> Result<학습과제, string> {
    let = 분해석(분문자열)?
    Ok(학습과제 { 제목: 제목, : , 완료: 완료 })
}

export function 요약(과제들: Array<학습과제>) -> string {
    let mut 남음 = 0
    for 과제 in 과제들 {
        if !과제.완료 {
            남음 = 남음 + 과제.
        }
    }
    "tasks={과제들.length}, remaining={남음} minutes"
}

export 키워드를 지정한 식별자만 외부 모듈에 노출됩니다. 분해석 함수는 모듈 내부 구현으로 남습니다.

exportimport는 나누어진 모듈 사이에서 식별자의 공개 범위를 제어합니다. export는 작성한 레코드나 함수를 다른 모듈에서 불러올 수 있도록 외부에 공개하는 구문입니다. src/plan.tpz 모듈은 학습과제, 과제만들기, 요약export하여 외부에 제공하는 반면, export가 없는 분해석 함수는 외부 접근을 막고 모듈 내부 전용 도우미 연산으로 캡슐화합니다.

src/main.tpz 파일은 노출된 식별자를 가져오고 패키지 진입점을 제공합니다.

TOPAZ
import src.plan { 학습과제, 과제만들기, 요약 }

export function main(인자: Array<string>, 표준입력: string) -> Result<int, string> {
    let 과제들: Array<학습과제> = [
        과제만들기("Run first program", "10", true)?,
        과제만들기("Build application", "25", false)?,
    ]
    print(요약(과제들))
    Ok(0)
}

import 구문은 다른 모듈이 export로 노출한 요소들을 가져와 현재 소스 코드 안에서 활용할 수 있게 연결합니다. src/main.tpzimport src.plan { 학습과제, 과제만들기, 요약 }을 선언해 앞서 src/plan.tpz에 만든 도메인 모델과 함수들을 가져와 데이터 목록을 구성하고 결과를 출력합니다.

명시적인 main 함수는 명령줄 인자와 표준 입력을 전달받습니다. 반환값은 종료 코드 또는 오류 메시지 문자열입니다. 이 애플리케이션은 두 값을 직접 사용하지 않지만, 함수 시그니처가 실행 경계를 명확히 나타냅니다.

도메인 로직과 진입점을 작성한 후에는 도메인 연산이 정상 작동하는지 검증하는 테스트 코드가 필요합니다. tests/plan.tpz 파일을 추가하세요.

TOPAZ
import src.plan { 학습과제, 과제만들기, 요약 }

match 과제만들기("Read syntax map", "15") {
    case Ok(과제) => assert(과제. == 15, "valid minutes")
    case Err(메시지) => assert(false, 메시지)
}

match 과제만들기("Read syntax map", "later") {
    case Ok(_) => assert(false, "invalid minutes were accepted")
    case Err(메시지) => assert(
        메시지 == "minutes must be a positive integer",
        "invalid minutes",
    )
}

let 과제들: Array<학습과제> = [
    학습과제 { 제목: "Run first program", : 10, 완료: true },
    학습과제 { 제목: "Build application", : 25 },
]
assert(
    요약(과제들) == "tasks=2, remaining=25 minutes",
    "summary",
)

tests/plan.tpz 파일 역시 import 구문을 통해 src/plan.tpz가 제공하는 식별자들을 불러와 테스트 항목을 구성합니다. 도메인 모듈 src/plan.tpz를 먼저 작성한 다음 이를 참조하는 src/main.tpztests/plan.tpz를 뒤따라 만드는 순서가 성립합니다.

격리된 TestHost 환경이 테스트에서 사용하는 정본 assert(...) 함수를 제공합니다. 이 파일은 프로덕션 코드를 변경하지 않으며, 유효한 입력, 유효하지 않은 입력, 최종 요약 결과를 검증합니다.

잠그고, 포맷하고, 검사하고, 테스트하고, 실행하기

소스와 테스트 생성을 마친 후에는 코드 검사와 실행 단계에 진입합니다. 가장 먼저 의존성 상태와 매니페스트 해시를 고정하는 작업이 요구됩니다.

의존성 잠금 파일을 생성하고 내용을 확인합니다.

BASH
topaz lock --root .

topaz lock --root . 명령은 현재 디렉터리의 topaz.toml 메타데이터를 기반으로 topaz.lock 록파일을 생성합니다. 록파일이 미리 존재해야만 이후 실행할 품질 검사, 테스트, 실행 명령에서 --locked 옵션을 지정하여 잠긴 의존성 조건을 유지할 수 있습니다.

생성된 topaz.lock 파일은 다음과 같습니다.

TOML
[[package]]
name = "study-plan"
version = "0.1.0"
source = "root"
manifest_hash = "sha256:780ff549a9fb9b09d27c62df8ce48e70c1fc7b33d6d429e6bb826685f42e7615"

생성된 topaz.lock에는 패키지 소스 정보와 topaz.toml 해시값인 manifest_hash가 저장됩니다. 록파일 생성이 끝났다면 로컬 품질 검사를 순서대로 실행합니다.

BASH
topaz fmt --check --root .
topaz check --root . --locked
topaz test tests/plan.tpz --root . --locked

이 세 명령은 소스 포맷팅 검사(fmt), 전체 구문 및 타입 검사(check), 독립 테스트 구동(test) 순으로 연결됩니다. 소스 코드 스타일의 부합 여부를 검출한 뒤 컴파일 유닛 전체의 정합성을 검사하고 최종적으로 단위 테스트를 가동하는 것이 바른 검증 순서입니다.

fmt --check 옵션은 소스 코드를 수정하지 않고 포맷 차이만 검출합니다. check 명령은 해석된 컴파일 유닛 전체를 검사합니다. 앞 단계에서 topaz lock으로 topaz.lock을 마련해 두었으므로 --locked 옵션을 전달해 검사 환경을 고정할 수 있습니다. 명시한 경로는 지정한 단일 테스트 진입점만 선택합니다. 테스트 결과는 다음과 같습니다.

출력
tests/plan.tpz: test-ok

tests/plan.tpz: test-ok 결과는 테스트 파일 안의 모든 assert 구문이 정상 통과했음을 나타냅니다. 소스 검사와 테스트를 모두 마쳤으므로 이제 애플리케이션 패키지를 직접 구동합니다.

패키지 진입점을 실행합니다.

BASH
topaz run --root . --locked

topaz run --root . --locked 명령은 topaz.tomlentry 항목이 가리키는 src/main.tpz 소스의 main 함수를 실행합니다. 앞선 단계에서 lock으로 기준을 고정하고 checktest로 코드를 점검했기 때문에 안정된 상태로 애플리케이션 진입점을 가동하게 됩니다.

다음 단일 라인이 출력됩니다.

출력
tasks=2, remaining=25 minutes

이 출력은 src/main.tpzsrc/plan.tpz 모듈의 과제 생성 및 요약 함수를 정상적으로 불러와 결과를 계산하고 화면에 찍어냈음을 보여줍니다.

토파즈 프로젝트 없이 빌드하고 실행하기

패키지가 소스 수준에서 정상 작동하면 외부 환경에 배포할 아티팩트를 만들어낼 차례입니다.

스캐폴드의 기본 빌드 타깃은 native입니다. 명령 실행 시 다른 타깃을 명시할 수도 있습니다. 이 과정에서 사용할 Python 아티팩트를 빌드하세요.

BASH
topaz build --target python --root . --locked --out-dir ../study-plan-product

topaz build --target python --root . --locked --out-dir ../study-plan-product 명령은 앞서 검증하고 잠근(--locked) 토파즈 소스를 컴파일하여 지정한 출력 디렉터리(../study-plan-product)에 독립 실행 가능한 Python 아티팩트를 아티팩트로 생성합니다.

타깃으로 Python을 선택한 이유가 있습니다. 실행 시 Python 3.11 이상이 필요하지만, 입문 과정에서 Rust 설치 단계까지 추가하지 않아도 되기 때문입니다. 관리 대상 출력 디렉터리에는 다음 파일이 포함됩니다.

study-plan-product/
├── GENERATED-OUTPUT-NOTICE.txt
├── LICENSE
├── NOTICE
├── program.py
├── topaz-artifact.json
└── topaz_py_rt.py

topaz build 명령을 실행하면 출력 디렉터리 안에 생성된 Python 실행 코드 program.py, 런타임 파일 topaz_py_rt.py, 메타데이터 topaz-artifact.json, 고지 문서를 포함한 파일 6개가 새로 만들어집니다.

이제 빌드 아티팩트가 토파즈 프로젝트와 소스 코드에 의존하지 않고 독립 구동할 수 있는지 확인합니다. 토파즈 프로젝트 전체를 다른 디렉터리로 이동한 후 아티팩트 디렉터리에서 실행합니다.

BASH
cd ..
mv study-plan source-unavailable
cd study-plan-product
python3 program.py

이 명령 조합은 다음과 같은 이유로 순서대로 실행합니다. 먼저 cd ..로 상위 폴더로 이동한 뒤 mv study-plan source-unavailable을 실행해 원본 토파즈 소스 디렉터리의 이름을 변경합니다. 이는 .tpz 소스 파일을 완전히 제거하거나 차단한 상황을 연출하기 위함입니다. 그 다음 cd study-plan-product로 아티팩트 폴더에 들어가 python3 program.py로 아티팩트를 구동합니다. 앞선 topaz build 단계에서 필요한 아티팩트 파일이 이미 준비되었기 때문에 소스 프로젝트를 치운 상태에서도 실행이 가능합니다.

PowerShell 환경에서는 이동 명령을 Rename-Item study-plan source-unavailable로 변경하세요. 실행 시에는 python program.py를 사용하세요. 패키지 실행 시와 바이트 단위로 동일한 결과가 출력됩니다.

출력
tasks=2, remaining=25 minutes

빌드 아티팩트 내부에는 생성된 Python 코드와 런타임 지원 파일이 포함되어 있습니다. 여기서 “원본 없이”란 .tpz 소스 파일이 존재하지 않음을 뜻합니다. 따라서 토파즈 프로젝트, 컴파일러, 레지스트리, 빌드 워크스페이스가 필요하지 않습니다. topaz build 명령이 생성해 둔 program.pytopaz_py_rt.py만으로 외부 환경에서 애플리케이션이 정상 동작함을 입증할 수 있습니다.

경계 여섯 개 구분하기

  • 파일.tpz 소스 파일 단일 항목입니다.
  • 모듈은 소스 파일이 export로 노출하는 네임스페이스입니다.
  • 유닛은 진입점과 가져오기 구문에서 함께 해석되는 토파즈 파일의 집합입니다.
  • 패키지topaz.tomltopaz.lock에 아티팩트 식별 정보와 잠근 의존성을 기록하는 단위입니다.
  • 타깃은 Python과 같이 어떠한 형태로 결과물을 생성할지 선택하는 빌드 타깃입니다.
  • 아티팩트는 빌드 프로세스가 생성한 관리 타깃 결과물로, 배포하여 독립 실행할 수 있습니다.

선택: 소스 작업 흐름인가, 제품 경계인가?

토파즈 프로젝트를 수정하는 동안에는 check·test·run 명령으로 소스 수준의 피드백을 빠르게 확인하세요. 실제 배포 산출물을 검증할 때는 build 명령을 실행하세요. 생성된 아티팩트는 프로젝트 외부에서 테스트하세요. 소스 트리 내부에서만 실행하면 프로그램의 정상 작동은 증명할 수 있지만 배포 경계에서의 정상 작동까지 증명하지는 못합니다.

직접 해보기

src/main.tpz 파일에서 두 번째 작업의 소요 시간 문자열을 "25"에서 "30"으로 변경하세요. 전체 실행 흐름을 통과하려면 어떠한 예상 결과를 수정해야 할까요?

정답 보기

패키지 실행과 Python 아티팩트 실행 결과는 tasks=2, remaining=30 minutes를 출력해야 합니다. 반면 지정한 테스트는 25분짜리 작업을 자체적으로 생성합니다. 따라서 기존 요약 검증 로직이 그대로 유효하며 테스트도 계속 통과합니다. 이는 테스트가 main 모듈의 데이터 상태에 의존하지 않고 자체 픽스처를 보유함을 의미합니다.

다음 단계로 넘어갈 준비

위 6가지 경계를 설명하고, 잠금 파일을 다시 생성하여 의존성을 고정하며, fmt --check·check·지정한 test·run을 실행하고, .tpz 프로젝트를 이동한 후 Python 아티팩트까지 실행할 수 있다면 입문 과정을 완료한 것입니다.

문법 한눈에 보기를 지도로 참고하고, 필요한 부분은 모듈과 가시성·애플리케이션 루프·아티팩트와 배포 문서에서 자세히 살펴보세요.