diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 57b0cd3..7efed85 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -50,6 +50,7 @@ steps: label: ":cityscape: acme build" build: message: ":test_tube: test runreal/cli ${BUILDKITE_BRANCH}-${BUILDKITE_COMMIT}" + branch: ${RUNREAL_ACME_BRANCH:-main} env: RUNREAL_FROM_SOURCE: true RUNREAL_FROM_REF: $BUILDKITE_COMMIT diff --git a/.gitignore b/.gitignore index 6e536f2..6493e58 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build +!src/commands/build scratch/ # This file is generated by the runreal CLI during the test .runreal diff --git a/README.md b/README.md index 4f40409..4782761 100644 --- a/README.md +++ b/README.md @@ -24,20 +24,23 @@ ## Getting Started ```sh -# Compile the editor -runreal project compile Editor +# Build the editor +runreal project build Editor -# Compile your project targets -runreal project compile Client +# Build your project targets +runreal project build Client # List available build targets -runreal list-targets +runreal info list-targets + +# Run your editor +runreal run Editor # Run your game -runreal project run +runreal run Game # Package your project -runreal project pkg -p Win64 -c Development +runreal project pkg Game Win64 Development nopak # Execute a buildgraph script runreal buildgraph run diff --git a/src/cmd.ts b/src/cmd.ts index cb72fcd..172fd60 100644 --- a/src/cmd.ts +++ b/src/cmd.ts @@ -4,18 +4,21 @@ import { Config } from './lib/config.ts' import { logger, LogLevel } from './lib/logger.ts' import { VERSION } from './version.ts' -import { debug } from './commands/debug/index.ts' +import { buildgraph } from './commands/buildgraph/index.ts' +import { run } from './commands/run/index.ts' import { engine } from './commands/engine/index.ts' +import { info } from './commands/info/index.ts' +import { build } from './commands/build/index.ts' +import { sln } from './commands/sln/index.ts' import { init } from './commands/init.ts' +import { cook } from './commands/cook.ts' +import { pkg } from './commands/pkg.ts' import { uat } from './commands/uat.ts' import { ubt } from './commands/ubt.ts' -import { buildgraph } from './commands/buildgraph/index.ts' import { workflow } from './commands/workflow/index.ts' import { script } from './commands/script.ts' import { uasset } from './commands/uasset/index.ts' import { auth } from './commands/auth.ts' -import { project } from './commands/project/index.ts' -import { listTargets } from './commands/list-targets.ts' const LogLevelType = new EnumType(LogLevel) export const cmd = new Command() @@ -51,15 +54,18 @@ export const cli = cmd .action(function () { this.showHelp() }) - .command('init', init) - .command('debug', debug) - .command('list-targets', listTargets) + .command('buildgraph', buildgraph) + .command('run', run) .command('engine', engine) + .command('build', build) + .command('cook', cook) + .command('pkg', pkg) + .command('sln', sln) + .command('uasset', uasset) + .command('workflow', workflow) + .command('info', info) + .command('init', init) .command('uat', uat) .command('ubt', ubt) - .command('buildgraph', buildgraph) - .command('workflow', workflow) .command('script', script) .command('auth', auth) - .command('uasset', uasset) - .command('project', project) diff --git a/src/commands/project/compile.ts b/src/commands/build/clean.ts similarity index 52% rename from src/commands/project/compile.ts rename to src/commands/build/clean.ts index 2643c57..e9965d3 100644 --- a/src/commands/project/compile.ts +++ b/src/commands/build/clean.ts @@ -1,35 +1,45 @@ -import { Command, EnumType, ValidationError } from '@cliffy/command' +import { Command, EnumType } from '@cliffy/command' import { Engine, EngineConfiguration, EnginePlatform, EngineTarget } from '../../lib/engine.ts' import { createProject } from '../../lib/project.ts' import type { GlobalOptions } from '../../lib/types.ts' import { Config } from '../../lib/config.ts' -export type CompileOptions = typeof compile extends Command +export type CleanOptions = typeof clean extends Command ? Options : never -export const compile = new Command() - .description('Compile a project') +export const clean = new Command() + .description('Cleans the output of a target build') + .type('Target', new EnumType(EngineTarget)) .type('Configuration', new EnumType(EngineConfiguration)) .type('Platform', new EnumType(EnginePlatform)) - .option('-p, --platform ', 'Platform', { default: Engine.getCurrentPlatform() }) - .option('-c, --configuration ', 'Configuration', { + .arguments(' [ubtArgs...]') + .option('--projected', 'Add the -project argument. Defaults to true', { default: true }) + .option('-p, --platform ', 'Platform to build, defaults to host platform', { + default: Engine.getCurrentPlatform(), + }) + .option('-c, --configuration ', 'Configuration to build, defaults to Development', { default: EngineConfiguration.Development, }) .option('--dry-run', 'Dry run', { default: false }) - .arguments('') - .action(async (options, target = EngineTarget.Editor) => { - const { platform, configuration, dryRun } = options as CompileOptions + .stopEarly() + .action(async (options, target = EngineTarget.Editor, ...ubtArgs: Array) => { + const { platform, configuration, dryRun, projected } = options as CleanOptions + const config = Config.getInstance() const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ cliOptions: options, }) const project = await createProject(enginePath, projectPath) + await project.compile({ target: target as EngineTarget, configuration: configuration as EngineConfiguration, platform: platform as EnginePlatform, dryRun: dryRun, + clean: true, + projected: projected, + extraArgs: ubtArgs, }) }) diff --git a/src/commands/build/client.ts b/src/commands/build/client.ts new file mode 100644 index 0000000..42270fc --- /dev/null +++ b/src/commands/build/client.ts @@ -0,0 +1,60 @@ +import { Command, EnumType } from '@cliffy/command' + +import { Engine, EngineConfiguration, EnginePlatform, EngineTarget } from '../../lib/engine.ts' +import { createProject } from '../../lib/project.ts' +import type { GlobalOptions } from '../../lib/types.ts' +import { Config } from '../../lib/config.ts' + +export type CompileOptions = typeof client extends Command + ? Options + : never + +export const client = new Command() + .description('Builds the client runtime') + .type('Configuration', new EnumType(EngineConfiguration)) + .type('Platform', new EnumType(EnginePlatform)) + .arguments('[ubtArgs...]') + .option('--projected', 'Add the -project argument. Defaults to true', { default: true }) + .option('-p, --platform ', 'Platform to build, defaults to host platform', { + default: Engine.getCurrentPlatform(), + }) + .option('-c, --configuration ', 'Configuration to build, defaults to Development', { + default: EngineConfiguration.Development, + }) + .option('--clean', 'Clean the current build first', { default: false }) + .option('--nouht', 'Skips building UnrealHeaderTool', { default: false }) + .option('--noxge', 'Disables Incredibuild', { default: true }) + .option('--dry-run', 'Dry run', { default: false }) + .stopEarly() + .action(async (options, ...ubtArgs: Array) => { + const { platform, configuration, dryRun, clean, nouht, noxge, projected } = options as CompileOptions + + const config = Config.getInstance() + const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ + cliOptions: options, + }) + const project = await createProject(enginePath, projectPath) + + if (clean) { + await project.compile({ + target: EngineTarget.Client, + configuration: configuration as EngineConfiguration, + platform: platform as EnginePlatform, + dryRun: dryRun, + clean: true, + projected: projected, + }) + } + + await project.compile({ + target: EngineTarget.Client, + configuration: configuration as EngineConfiguration, + platform: platform as EnginePlatform, + dryRun: dryRun, + extraArgs: ubtArgs, + clean: false, + nouht: nouht, + noxge: noxge, + projected: projected, + }) + }) diff --git a/src/commands/build/editor.ts b/src/commands/build/editor.ts new file mode 100644 index 0000000..28944b5 --- /dev/null +++ b/src/commands/build/editor.ts @@ -0,0 +1,60 @@ +import { Command, EnumType } from '@cliffy/command' + +import { Engine, EngineConfiguration, EnginePlatform, EngineTarget } from '../../lib/engine.ts' +import { createProject } from '../../lib/project.ts' +import type { GlobalOptions } from '../../lib/types.ts' +import { Config } from '../../lib/config.ts' + +export type CompileOptions = typeof editor extends Command + ? Options + : never + +export const editor = new Command() + .description('Builds the editor') + .type('Configuration', new EnumType(EngineConfiguration)) + .type('Platform', new EnumType(EnginePlatform)) + .arguments('[ubtArgs...]') + .option('--projected', 'Add the -project argument. Defaults to true', { default: true }) + .option('-p, --platform ', 'Platform to build, defaults to host platform', { + default: Engine.getCurrentPlatform(), + }) + .option('-c, --configuration ', 'Configuration to build, defaults to Development', { + default: EngineConfiguration.Development, + }) + .option('--clean', 'Clean the current build first', { default: false }) + .option('--nouht', 'Skips building UnrealHeaderTool', { default: false }) + .option('--noxge', 'Disables Incredibuild', { default: true }) + .option('--dry-run', 'Dry run', { default: false }) + .stopEarly() + .action(async (options, ...ubtArgs: Array) => { + const { platform, configuration, dryRun, clean, nouht, noxge, projected } = options as CompileOptions + + const config = Config.getInstance() + const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ + cliOptions: options, + }) + const project = await createProject(enginePath, projectPath) + + if (clean) { + await project.compile({ + target: EngineTarget.Editor, + configuration: configuration as EngineConfiguration, + platform: platform as EnginePlatform, + dryRun: dryRun, + clean: true, + projected: projected, + }) + } + + await project.compile({ + target: EngineTarget.Editor, + configuration: configuration as EngineConfiguration, + platform: platform as EnginePlatform, + dryRun: dryRun, + extraArgs: ubtArgs, + clean: false, + nouht: nouht, + noxge: noxge, + projected: projected, + }) + }) diff --git a/src/commands/build/game.ts b/src/commands/build/game.ts new file mode 100644 index 0000000..5e2170e --- /dev/null +++ b/src/commands/build/game.ts @@ -0,0 +1,60 @@ +import { Command, EnumType } from '@cliffy/command' + +import { Engine, EngineConfiguration, EnginePlatform, EngineTarget } from '../../lib/engine.ts' +import { createProject } from '../../lib/project.ts' +import type { GlobalOptions } from '../../lib/types.ts' +import { Config } from '../../lib/config.ts' + +export type CompileOptions = typeof game extends Command + ? Options + : never + +export const game = new Command() + .description('Builds the game runtime') + .type('Configuration', new EnumType(EngineConfiguration)) + .type('Platform', new EnumType(EnginePlatform)) + .arguments('[ubtArgs...]') + .option('--projected', 'Add the -project argument. Defaults to true', { default: true }) + .option('-p, --platform ', 'Platform to build, defaults to host platform', { + default: Engine.getCurrentPlatform(), + }) + .option('-c, --configuration ', 'Configuration to build, defaults to Development', { + default: EngineConfiguration.Development, + }) + .option('--clean', 'Clean the current build first', { default: false }) + .option('--nouht', 'Skips building UnrealHeaderTool', { default: false }) + .option('--noxge', 'Disables Incredibuild', { default: true }) + .option('--dry-run', 'Dry run', { default: false }) + .stopEarly() + .action(async (options, ...ubtArgs: Array) => { + const { platform, configuration, dryRun, clean, nouht, noxge, projected } = options as CompileOptions + + const config = Config.getInstance() + const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ + cliOptions: options, + }) + const project = await createProject(enginePath, projectPath) + + if (clean) { + await project.compile({ + target: EngineTarget.Game, + configuration: configuration as EngineConfiguration, + platform: platform as EnginePlatform, + dryRun: dryRun, + clean: true, + projected: projected, + }) + } + + await project.compile({ + target: EngineTarget.Game, + configuration: configuration as EngineConfiguration, + platform: platform as EnginePlatform, + dryRun: dryRun, + extraArgs: ubtArgs, + clean: false, + nouht: nouht, + noxge: noxge, + projected: projected, + }) + }) diff --git a/src/commands/build/index.ts b/src/commands/build/index.ts new file mode 100644 index 0000000..5a9733e --- /dev/null +++ b/src/commands/build/index.ts @@ -0,0 +1,22 @@ +import { Command } from '@cliffy/command' + +import type { GlobalOptions } from '../../lib/types.ts' + +import { client } from './client.ts' +import { clean } from './clean.ts' +import { editor } from './editor.ts' +import { game } from './game.ts' +import { program } from './program.ts' +import { server } from './server.ts' + +export const build = new Command() + .description('build') + .action(function () { + this.showHelp() + }) + .command('client', client) + .command('clean', clean) + .command('editor', editor) + .command('game', game) + .command('program', program) + .command('server', server) diff --git a/src/commands/build/program.ts b/src/commands/build/program.ts new file mode 100644 index 0000000..917457f --- /dev/null +++ b/src/commands/build/program.ts @@ -0,0 +1,60 @@ +import { Command, EnumType } from '@cliffy/command' + +import { Engine, EngineConfiguration, EnginePlatform, EngineTarget } from '../../lib/engine.ts' +import { createProject } from '../../lib/project.ts' +import type { GlobalOptions } from '../../lib/types.ts' +import { Config } from '../../lib/config.ts' + +export type CompileOptions = typeof program extends Command + ? Options + : never + +export const program = new Command() + .description('Builds a program') + .type('Configuration', new EnumType(EngineConfiguration)) + .type('Platform', new EnumType(EnginePlatform)) + .arguments(' [ubtArgs...]') + .option('--projected', 'Add the -project argument. Defaults to false', { default: false }) + .option('-p, --platform ', 'Platform to build, defaults to host platform', { + default: Engine.getCurrentPlatform(), + }) + .option('-c, --configuration ', 'Configuration to build, defaults to Development', { + default: EngineConfiguration.Development, + }) + .option('--clean', 'Clean the current build first', { default: false }) + .option('--nouht', 'Skips building UnrealHeaderTool', { default: false }) + .option('--noxge', 'Disables Incredibuild', { default: true }) + .option('--dry-run', 'Dry run', { default: false }) + .stopEarly() + .action(async (options, program: string, ...ubtArgs: Array) => { + const { platform, configuration, dryRun, clean, nouht, noxge, projected } = options as CompileOptions + + const config = Config.getInstance() + const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ + cliOptions: options, + }) + const project = await createProject(enginePath, projectPath) + + if (clean) { + await project.compileTarget({ + target: program, + configuration: configuration as EngineConfiguration, + platform: platform as EnginePlatform, + dryRun: dryRun, + clean: true, + projected: projected, + }) + } + + await project.compileTarget({ + target: program, + configuration: configuration as EngineConfiguration, + platform: platform as EnginePlatform, + dryRun: dryRun, + extraArgs: ubtArgs, + clean: false, + nouht: nouht, + noxge: noxge, + projected: projected, + }) + }) diff --git a/src/commands/build/server.ts b/src/commands/build/server.ts new file mode 100644 index 0000000..f4c023c --- /dev/null +++ b/src/commands/build/server.ts @@ -0,0 +1,60 @@ +import { Command, EnumType } from '@cliffy/command' + +import { Engine, EngineConfiguration, EnginePlatform, EngineTarget } from '../../lib/engine.ts' +import { createProject } from '../../lib/project.ts' +import type { GlobalOptions } from '../../lib/types.ts' +import { Config } from '../../lib/config.ts' + +export type CompileOptions = typeof server extends Command + ? Options + : never + +export const server = new Command() + .description('Builds the server runtime') + .type('Configuration', new EnumType(EngineConfiguration)) + .type('Platform', new EnumType(EnginePlatform)) + .arguments('[ubtArgs...]') + .option('--projected', 'Add the -project argument. Defaults to true', { default: true }) + .option('-p, --platform ', 'Platform to build, defaults to host platform', { + default: Engine.getCurrentPlatform(), + }) + .option('-c, --configuration ', 'Configuration to build, defaults to Development', { + default: EngineConfiguration.Development, + }) + .option('--clean', 'Clean the current build first', { default: false }) + .option('--nouht', 'Skips building UnrealHeaderTool', { default: false }) + .option('--noxge', 'Disables Incredibuild', { default: true }) + .option('--dry-run', 'Dry run', { default: false }) + .stopEarly() + .action(async (options, ...ubtArgs: Array) => { + const { platform, configuration, dryRun, clean, nouht, noxge, projected } = options as CompileOptions + + const config = Config.getInstance() + const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ + cliOptions: options, + }) + const project = await createProject(enginePath, projectPath) + + if (clean) { + await project.compile({ + target: EngineTarget.Server, + configuration: configuration as EngineConfiguration, + platform: platform as EnginePlatform, + dryRun: dryRun, + clean: true, + projected: projected, + }) + } + + await project.compile({ + target: EngineTarget.Server, + configuration: configuration as EngineConfiguration, + platform: platform as EnginePlatform, + dryRun: dryRun, + extraArgs: ubtArgs, + clean: false, + nouht: nouht, + noxge: noxge, + projected: projected, + }) + }) diff --git a/src/commands/cook.ts b/src/commands/cook.ts new file mode 100644 index 0000000..32fe039 --- /dev/null +++ b/src/commands/cook.ts @@ -0,0 +1,49 @@ +import { Command, EnumType } from '@cliffy/command' + +import { createProject } from '../lib/project.ts' +import type { GlobalOptions } from '../lib/types.ts' +import { Config } from '../lib/config.ts' +import { CookTarget } from '../lib/engine.ts' + +export type CookOptions = typeof cook extends Command + ? Options + : never + +export const cook = new Command() + .description('Cook content for the target') + .type('Target', new EnumType(CookTarget)) + .arguments(' [cookArguments...]') + .option('--cultures ', 'Comma separated string of cultures to cook, defaults to all', { + required: false, + }) + .option('--onthefly', 'Launch as an on-the-fly server', { default: false }) + .option('--iterate', 'Cook iteratively', { default: true }) + .option('--noxge', 'Disable XGE shader compilation', { default: true }) + .option('--debug', 'Use debug executables', { default: false }) + .option('--dry-run', 'Dry run', { default: false }) + .stopEarly() + .action(async (options, target = CookTarget.Windows, ...cookArguments: Array) => { + const { dryRun, noxge, debug, iterate, onthefly, cultures } = options as CookOptions + const config = Config.getInstance() + const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ + cliOptions: options, + }) + + const project = await createProject(enginePath, projectPath) + + let cultureArgs: string[] = [] + if (cultures) { + cultureArgs = cultures.replace(' ', '').split(',') + } + + await project.cookContent({ + target: target as CookTarget, + extraArgs: cookArguments, + cultures: cultureArgs, + onTheFly: onthefly, + iterate: iterate, + noxge: noxge, + debug: debug, + dryRun: dryRun, + }) + }) diff --git a/src/commands/debug/index.ts b/src/commands/debug/index.ts deleted file mode 100644 index 79ff529..0000000 --- a/src/commands/debug/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Command } from '@cliffy/command' -import type { GlobalOptions } from '../../lib/types.ts' - -import { debugConfig } from './debug-config.ts' -import { debugBuildId } from './debug-buildId.ts' - -export const debug = new Command() - .description('debug') - .action(function () { - this.showHelp() - }) - .command('config', debugConfig) - .command('buildId', debugBuildId) diff --git a/src/commands/debug/debug-buildId.ts b/src/commands/info/buildId.ts similarity index 78% rename from src/commands/debug/debug-buildId.ts rename to src/commands/info/buildId.ts index 4c1337a..2abdefa 100644 --- a/src/commands/debug/debug-buildId.ts +++ b/src/commands/info/buildId.ts @@ -2,11 +2,11 @@ import { Command } from '@cliffy/command' import { Config } from '../../lib/config.ts' import type { GlobalOptions } from '../../lib/types.ts' -export type DebugBuildIdOptions = typeof debugBuildId extends +export type DebugBuildIdOptions = typeof buildId extends Command, [], GlobalOptions> ? Options : never -export const debugBuildId = new Command() +export const buildId = new Command() .description('debug buildId') .action((options) => { const config = Config.getInstance() diff --git a/src/commands/debug/debug-config.ts b/src/commands/info/config.ts similarity index 85% rename from src/commands/debug/debug-config.ts rename to src/commands/info/config.ts index ba04ba6..b662c56 100644 --- a/src/commands/debug/debug-config.ts +++ b/src/commands/info/config.ts @@ -2,11 +2,11 @@ import { Command } from '@cliffy/command' import { Config } from '../../lib/config.ts' import type { GlobalOptions } from '../../lib/types.ts' -export type DebugConfigOptions = typeof debugConfig extends +export type DebugConfigOptions = typeof config extends Command, [], GlobalOptions> ? Options : never -export const debugConfig = new Command() +export const config = new Command() .option('-r, --render', 'Render the config with substitutions') .description('debug config') .action((options) => { diff --git a/src/commands/info/index.ts b/src/commands/info/index.ts new file mode 100644 index 0000000..7adecb5 --- /dev/null +++ b/src/commands/info/index.ts @@ -0,0 +1,15 @@ +import { Command } from '@cliffy/command' +import type { GlobalOptions } from '../../lib/types.ts' + +import { buildId } from './buildId.ts' +import { config } from './config.ts' +import { listTargets } from './list-targets.ts' + +export const info = new Command() + .description('info') + .action(function () { + this.showHelp() + }) + .command('buildId', buildId) + .command('config', config) + .command('list-targets', listTargets) diff --git a/src/commands/list-targets.ts b/src/commands/info/list-targets.ts similarity index 87% rename from src/commands/list-targets.ts rename to src/commands/info/list-targets.ts index 484cdc2..c4dc192 100644 --- a/src/commands/list-targets.ts +++ b/src/commands/info/list-targets.ts @@ -1,8 +1,8 @@ import { Command } from '@cliffy/command' -import { createProject } from '../lib/project.ts' -import { createEngine } from '../lib/engine.ts' -import type { GlobalOptions } from '../lib/types.ts' -import { Config } from '../lib/config.ts' +import { createProject } from '../../lib/project.ts' +import { createEngine } from '../../lib/engine.ts' +import type { GlobalOptions } from '../../lib/types.ts' +import { Config } from '../../lib/config.ts' export type ListTargetsOptions = typeof listTargets extends Command ? Options diff --git a/src/commands/pkg.ts b/src/commands/pkg.ts new file mode 100644 index 0000000..bee67cc --- /dev/null +++ b/src/commands/pkg.ts @@ -0,0 +1,93 @@ +import { Command, EnumType } from '@cliffy/command' +import { Config } from '../lib/config.ts' +import { createProject } from '../lib/project.ts' +import type { GlobalOptions } from '../lib/types.ts' +import { + CookStyle, + CookTarget, + EngineConfiguration, + EnginePlatform, + EngineTarget, + GameTarget, + getPlatformCookTarget, +} from '../lib/engine.ts' + +export type PkgOptions = typeof pkg extends Command ? Options + : never + +export const pkg = new Command() + .description('Package a project') + .type('Style', new EnumType(CookStyle)) + .type('Target', new EnumType(GameTarget)) + .type('Configuration', new EnumType(EngineConfiguration)) + .type('Platform', new EnumType(EnginePlatform)) + .arguments(' [uatArgs...]') + .option('--dry-run', 'Dry run', { default: false }) + .option('-z, --zip', 'Should we zip the archive') + .option( + '--buildargs ', + 'Build code prior to staging, comma separated list of arguments for the build', + ) + .option( + '--cookargs ', + 'Cook content prior to staging, comma separated list of arguments for the cook', + ) + .option('-a, --archive-directory ', 'Path to archive directory') + .stopEarly() + .action(async (options, target, platform, configuration, style, ...uatArgs: Array) => { + const { dryRun, zip, buildargs, cookargs, archiveDirectory } = options as PkgOptions + + const config = Config.getInstance() + const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ + cliOptions: options, + }) + const project = await createProject(enginePath, projectPath) + + const args = uatArgs + + switch (style) { + case CookStyle.pak: + args.push('-pak') + break + case CookStyle.zen: + args.push('-zen') + break + case CookStyle.nopak: + args.push('-nopak') + break + } + + if (buildargs) { + project.compile({ + target: target as GameTarget, + configuration: configuration as EngineConfiguration, + platform: platform as EnginePlatform, + dryRun: dryRun, + extraArgs: (buildargs as string).split(','), + }) + args.push('-skipbuild') + } else { + args.push('-build') + } + if (cookargs) { + const cookTarget = getPlatformCookTarget(platform, target) + project.cookContent({ + target: cookTarget as CookTarget, + dryRun: dryRun, + extraArgs: cookargs, + }) + args.push('-skipcook') + } else { + args.push('-cook') + } + + project.package({ + profile: target, + configuration: configuration, + extraArgs: args, + dryRun: dryRun, + platform: platform, + zip: zip, + archiveDirectory: archiveDirectory, + }) + }) diff --git a/src/commands/project/clean.ts b/src/commands/project/clean.ts deleted file mode 100644 index 544a293..0000000 --- a/src/commands/project/clean.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Command } from '@cliffy/command' -import { createProject } from '../../lib/project.ts' -import { Config } from '../../lib/config.ts' - -export const clean = new Command() - .option('--dry-run', 'Dry run', { default: false }) - .description('clean') - .action(async (options) => { - const config = Config.getInstance() - const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ - cliOptions: options, - }) - const project = await createProject(enginePath, projectPath) - project.runClean(options.dryRun) - }) diff --git a/src/commands/project/cook.ts b/src/commands/project/cook.ts deleted file mode 100644 index beea526..0000000 --- a/src/commands/project/cook.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Command } from '@cliffy/command' - -import { createProject } from '../../lib/project.ts' -import type { GlobalOptions } from '../../lib/types.ts' -import { Config } from '../../lib/config.ts' - -export const cook = new Command() - .description('Cook the project') - .arguments('') - .option('--dry-run', 'Dry run', { default: false }) - .option('--compile', 'Compile before Cook', { default: false }) - .stopEarly() - .action(async (options, ...cookArguments: Array) => { - const config = Config.getInstance() - const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ - cliOptions: options, - }) - - const project = await createProject(enginePath, projectPath) - if (options.compile) { - await project.compile({}) - } - - await project.cookContent({ extraArgs: cookArguments }) - }) diff --git a/src/commands/project/editor.ts b/src/commands/project/editor.ts deleted file mode 100644 index dc57116..0000000 --- a/src/commands/project/editor.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Command } from '@cliffy/command' - -import { createProject } from '../../lib/project.ts' -import type { GlobalOptions } from '../../lib/types.ts' -import { Config } from '../../lib/config.ts' - -export const editor = new Command() - .description('Run the editor') - .arguments('') - .option('--dry-run', 'Dry run', { default: false }) - .option('--compile', 'Compile binaries first', { default: false }) - .stopEarly() - .action(async (options, ...editorArguments: Array) => { - const config = Config.getInstance() - const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ - cliOptions: options, - }) - const project = await createProject(enginePath, projectPath) - - if (options.dryRun) { - console.log(`Would open editor with ${editorArguments}`) - Deno.exit() - } - - console.log(`Running editor with ${editorArguments}`) - - if (options.compile) { - await project.compileAndRunEditor({ extraRunArgs: editorArguments }) - } else { - await project.runEditor({ extraArgs: editorArguments }) - } - }) diff --git a/src/commands/project/index.ts b/src/commands/project/index.ts deleted file mode 100644 index 81708d9..0000000 --- a/src/commands/project/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Command } from '@cliffy/command' - -import type { GlobalOptions } from '../../lib/types.ts' - -import { clean } from './clean.ts' -import { compile } from './compile.ts' -import { cook } from './cook.ts' -import { editor } from './editor.ts' -import { gen } from './gen.ts' -import { pkg } from './pkg.ts' -import { run } from './run.ts' -import { runpython } from './runpython.ts' - -export const project = new Command() - .description('project') - .action(function () { - this.showHelp() - }) - .command('clean', clean) - .command('compile', compile) - .command('cook', cook) - .command('editor', editor) - .command('gen', gen) - .command('pkg', pkg) - .command('run', run) - .command('runpython', runpython) diff --git a/src/commands/project/pkg.ts b/src/commands/project/pkg.ts deleted file mode 100644 index 46c0e6e..0000000 --- a/src/commands/project/pkg.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Command, EnumType } from '@cliffy/command' -import { Engine, EngineConfiguration, EnginePlatform } from '../../lib/engine.ts' -import { Config } from '../../lib/config.ts' -import type { GlobalOptions } from '../../lib/types.ts' -import { createProject } from '../../lib/project.ts' - -export type PkgOptions = typeof pkg extends Command ? Options - : never - -export const pkg = new Command() - .description('package') - .type('Configuration', new EnumType(EngineConfiguration)) - .type('Platform', new EnumType(EnginePlatform)) - .arguments('') - .option('-p, --platform ', 'Platform', { default: Engine.getCurrentPlatform() }) - .option('-c, --configuration ', 'Configuration', { - default: EngineConfiguration.Development, - }) - .option('-a, --archive-directory ', 'Path to archive directory') - .option('-z, --zip', 'Should we zip the archive') - .option('-d, --dry-run', 'Dry run') - .option('--compile', 'Use the precompiled binaries', { default: false }) - .option('--profile ', 'Build profile', { default: 'client', required: true }) - .stopEarly() - .action(async (options, ...pkgArguments: Array) => { - const { platform, configuration, dryRun, profile, archiveDirectory, zip } = options as PkgOptions - const cfg = Config.getInstance() - const { engine: { path: enginePath }, project: { path: projectPath } } = cfg.mergeConfigCLIConfig({ - cliOptions: options, - }) - - const args = pkg.getLiteralArgs().concat(pkgArguments) - - const project = await createProject(enginePath, projectPath) - project.package({ archiveDirectory: archiveDirectory, profile: profile, extraArgs: args }) - }) diff --git a/src/commands/project/run.ts b/src/commands/project/run.ts deleted file mode 100644 index 9ebdf95..0000000 --- a/src/commands/project/run.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Command } from '@cliffy/command' - -import { createProject } from '../../lib/project.ts' -import type { GlobalOptions } from '../../lib/types.ts' -import { Config } from '../../lib/config.ts' - -export const run = new Command() - .description('Run the game') - .arguments('') - .option('--dry-run', 'Dry run', { default: false }) - .option('--compile', 'Use the precompiled binaries', { default: false }) - .stopEarly() - .action(async (options, ...runArguments: Array) => { - const config = Config.getInstance() - const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ - cliOptions: options, - }) - - const project = await createProject(enginePath, projectPath) - - if (options.compile) { - await project.runEditor({ extraArgs: ['-game', ...runArguments] }) - } else { - await project.compileAndRunEditor({ extraRunArgs: ['-game', ...runArguments] }) - } - }) diff --git a/src/commands/project/runpython.ts b/src/commands/project/runpython.ts deleted file mode 100644 index c1d70ca..0000000 --- a/src/commands/project/runpython.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Command } from '@cliffy/command' -import * as path from '@std/path' -import { Config } from '../../lib/config.ts' -import type { GlobalOptions } from '../../lib/types.ts' -import { createProject } from '../../lib/project.ts' - -export type RunPythonOptions = typeof runpython extends - Command ? Options - : never - -export const runpython = new Command() - .description('Run Python script in Unreal Engine headless mode') - .option('-s, --script ', 'Path to Python script', { required: true }) - .option('--stdout', 'Redirect output to stdout', { default: true }) - .option('--nosplash', 'Skip splash screen', { default: true }) - .option('--nopause', "Don't pause after execution", { default: true }) - .option('--nosound', 'Disable sound', { default: true }) - .option('--args ', 'Additional arguments to pass to the script') - .action(async (options) => { - const config = Config.getInstance() - const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ - cliOptions: options, - }) - const project = await createProject(enginePath, projectPath) - - // Resolve absolute path to script - const scriptPath = path.resolve(options.script) - - // Build command arguments - const args = [ - '-unattended', - ] - - // Add optional flags - if (options.stdout) args.push('-stdout') - if (options.nosplash) args.push('-nosplash') - if (options.nopause) args.push('-nopause') - if (options.nosound) args.push('-nosound') - - // Add any additional arguments - if (options.args) { - args.push(options.args) - } - - project.runPython(scriptPath, args) - }) diff --git a/src/commands/run/client.ts b/src/commands/run/client.ts new file mode 100644 index 0000000..2b8b6b4 --- /dev/null +++ b/src/commands/run/client.ts @@ -0,0 +1,36 @@ +import { Command } from '@cliffy/command' + +import { createProject } from '../../lib/project.ts' +import type { GlobalOptions } from '../../lib/types.ts' +import { Config } from '../../lib/config.ts' +import { EngineConfiguration, EnginePlatform, EngineTarget } from '../../lib/engine.ts' + +export type ClientOptions = typeof client extends Command + ? Options + : never + +export const client = new Command() + .description('Launches the game client') + .arguments(' [runArguments...]>') + .option('--dry-run', 'Dry run', { default: false }) + .option('--compile', 'Build the target prior to running it', { default: false }) + .stopEarly() + .action(async (options, configuration = EngineConfiguration.Development, ...runArguments: Array) => { + const { dryRun, compile } = options as ClientOptions + const config = Config.getInstance() + const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ + cliOptions: options, + }) + + const project = await createProject(enginePath, projectPath) + + if (compile) { + await project.compile({ + target: EngineTarget.Client, + configuration: configuration as EngineConfiguration, + dryRun: options.dryRun, + }) + } + + await project.runEditor({ extraArgs: ['-client', ...runArguments] }) + }) diff --git a/src/commands/run/commandlet.ts b/src/commands/run/commandlet.ts new file mode 100644 index 0000000..8e33f8b --- /dev/null +++ b/src/commands/run/commandlet.ts @@ -0,0 +1,52 @@ +import { Command } from '@cliffy/command' + +import { createProject } from '../../lib/project.ts' +import type { GlobalOptions } from '../../lib/types.ts' +import { Config } from '../../lib/config.ts' +import { EngineConfiguration, EnginePlatform, EngineTarget } from '../../lib/engine.ts' + +export type CommandletOptions = typeof commandlet extends + Command ? Options + : never + +export const commandlet = new Command() + .description('Run commandlet Unreal Engine headless mode') + .arguments(' [runArguments...]') + .option('--dry-run', 'Dry run', { default: false }) + .option('--compile', 'Build the target prior to running it', { default: false }) + .stopEarly() + .action( + async ( + options, + configuration = EngineConfiguration.Development, + commandletName, + ...runArguments: Array + ) => { + const { dryRun, compile } = options as CommandletOptions + const config = Config.getInstance() + const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ + cliOptions: options, + }) + + const project = await createProject(enginePath, projectPath) + + if (compile) { + await project.compile({ + target: EngineTarget.Editor, + configuration: configuration as EngineConfiguration, + dryRun: options.dryRun, + }) + } + + const args = [ + `-run=${commandletName}`, + '-stdout', + '-nosplash', + '-nopause', + '-nosound', + ...runArguments, + ] + + await project.runEditor({ extraArgs: args, useCmd: true }) + }, + ) diff --git a/src/commands/run/editor.ts b/src/commands/run/editor.ts new file mode 100644 index 0000000..6cd3c39 --- /dev/null +++ b/src/commands/run/editor.ts @@ -0,0 +1,36 @@ +import { Command } from '@cliffy/command' + +import { createProject } from '../../lib/project.ts' +import type { GlobalOptions } from '../../lib/types.ts' +import { Config } from '../../lib/config.ts' +import { EngineConfiguration, EnginePlatform, EngineTarget } from '../../lib/engine.ts' + +export type EditorOptions = typeof editor extends Command + ? Options + : never + +export const editor = new Command() + .description('Run the game') + .arguments(' [runArguments...]>') + .option('--dry-run', 'Dry run', { default: false }) + .option('--compile', 'Build the target prior to running it', { default: false }) + .stopEarly() + .action(async (options, configuration = EngineConfiguration.Development, ...runArguments: Array) => { + const { dryRun, compile } = options as EditorOptions + const config = Config.getInstance() + const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ + cliOptions: options, + }) + + const project = await createProject(enginePath, projectPath) + + if (compile) { + await project.compile({ + target: EngineTarget.Editor, + configuration: configuration as EngineConfiguration, + dryRun: options.dryRun, + }) + } + + await project.runEditor({ extraArgs: [...runArguments] }) + }) diff --git a/src/commands/run/game.ts b/src/commands/run/game.ts new file mode 100644 index 0000000..7fdfe71 --- /dev/null +++ b/src/commands/run/game.ts @@ -0,0 +1,36 @@ +import { Command } from '@cliffy/command' + +import { createProject } from '../../lib/project.ts' +import type { GlobalOptions } from '../../lib/types.ts' +import { Config } from '../../lib/config.ts' +import { EngineConfiguration, EnginePlatform, EngineTarget } from '../../lib/engine.ts' + +export type GameOptions = typeof game extends Command + ? Options + : never + +export const game = new Command() + .description('Launches the game') + .arguments(' [runArguments...]') + .option('--dry-run', 'Dry run', { default: false }) + .option('--compile', 'Build the target prior to running it', { default: false }) + .stopEarly() + .action(async (options, configuration = EngineConfiguration.Development, ...runArguments: Array) => { + const { dryRun, compile } = options as GameOptions + const config = Config.getInstance() + const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ + cliOptions: options, + }) + + const project = await createProject(enginePath, projectPath) + + if (compile) { + await project.compile({ + target: EngineTarget.Game, + configuration: configuration as EngineConfiguration, + dryRun: options.dryRun, + }) + } + + await project.runEditor({ extraArgs: ['-game', ...runArguments] }) + }) diff --git a/src/commands/run/index.ts b/src/commands/run/index.ts new file mode 100644 index 0000000..1691ac5 --- /dev/null +++ b/src/commands/run/index.ts @@ -0,0 +1,21 @@ +import { Command } from '@cliffy/command' +import type { GlobalOptions } from '../../lib/types.ts' + +import { client } from './client.ts' +import { commandlet } from './commandlet.ts' +import { editor } from './editor.ts' +import { game } from './game.ts' +import { python } from './python.ts' +import { server } from './server.ts' + +export const run = new Command() + .description('Run the game, editor, or commandlet') + .action(function () { + this.showHelp() + }) + .command('client', client) + .command('commandlet', commandlet) + .command('editor', editor) + .command('game', game) + .command('python', python) + .command('server', server) diff --git a/src/commands/run/python.ts b/src/commands/run/python.ts new file mode 100644 index 0000000..3642964 --- /dev/null +++ b/src/commands/run/python.ts @@ -0,0 +1,50 @@ +import { Command } from '@cliffy/command' + +import { createProject } from '../../lib/project.ts' +import type { GlobalOptions } from '../../lib/types.ts' +import { Config } from '../../lib/config.ts' +import { EngineConfiguration, EnginePlatform, EngineTarget } from '../../lib/engine.ts' + +export type PythonOptions = typeof python extends Command + ? Options + : never + +export const python = new Command() + .description('Run Python script in Unreal Engine headless mode') + .arguments(' [runArguments...]') + .option('--dry-run', 'Dry run', { default: false }) + .option('--compile', 'Build the target prior to running it', { default: false }) + .stopEarly() + .action( + async (options, configuration = EngineConfiguration.Development, scriptPath, ...runArguments: Array) => { + const { dryRun, compile } = options as PythonOptions + const config = Config.getInstance() + const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ + cliOptions: options, + }) + + const project = await createProject(enginePath, projectPath) + + if (compile) { + await project.compile({ + target: EngineTarget.Editor, + configuration: configuration as EngineConfiguration, + dryRun: options.dryRun, + }) + } + + const args = [ + '-run=pythonscript', + `-script=${scriptPath}`, + '-stdout', + '-nosplash', + '-nopause', + '-nosound', + ...runArguments, + ] + + console.log(`Running Python script: ${scriptPath}`) + + await project.runEditor({ extraArgs: args, useCmd: true }) + }, + ) diff --git a/src/commands/run/server.ts b/src/commands/run/server.ts new file mode 100644 index 0000000..425b711 --- /dev/null +++ b/src/commands/run/server.ts @@ -0,0 +1,36 @@ +import { Command } from '@cliffy/command' + +import { createProject } from '../../lib/project.ts' +import type { GlobalOptions } from '../../lib/types.ts' +import { Config } from '../../lib/config.ts' +import { EngineConfiguration, EnginePlatform, EngineTarget } from '../../lib/engine.ts' + +export type ServerOptions = typeof server extends Command + ? Options + : never + +export const server = new Command() + .description('Launches the game server') + .arguments(' [runArguments...]') + .option('--dry-run', 'Dry run', { default: false }) + .option('--compile', 'Build the target prior to running it', { default: false }) + .stopEarly() + .action(async (options, configuration = EngineConfiguration.Development, ...runArguments: Array) => { + const { dryRun, compile } = options as ServerOptions + const config = Config.getInstance() + const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ + cliOptions: options, + }) + + const project = await createProject(enginePath, projectPath) + + if (compile) { + await project.compile({ + target: EngineTarget.Server, + configuration: configuration as EngineConfiguration, + dryRun: options.dryRun, + }) + } + + await project.runEditor({ extraArgs: ['-server', ...runArguments] }) + }) diff --git a/src/commands/project/gen.ts b/src/commands/sln/generate.ts similarity index 73% rename from src/commands/project/gen.ts rename to src/commands/sln/generate.ts index 4dbd13a..6254e8a 100644 --- a/src/commands/project/gen.ts +++ b/src/commands/sln/generate.ts @@ -2,13 +2,13 @@ import { Command } from '@cliffy/command' import { Config } from '../../lib/config.ts' import { createProject } from '../../lib/project.ts' -export type GenOptions = typeof gen extends Command, [], void> - ? Options +export type GenOptions = typeof generate extends + Command, [], void> ? Options : never -export const gen = new Command() +export const generate = new Command() .description('generate') - .arguments('') + .arguments('[genArguments...]') .option('--dry-run', 'Dry run', { default: false }) .stopEarly() .action(async (options, ...genArguments: Array) => { diff --git a/src/commands/sln/index.ts b/src/commands/sln/index.ts new file mode 100644 index 0000000..7f4e985 --- /dev/null +++ b/src/commands/sln/index.ts @@ -0,0 +1,13 @@ +import { Command } from '@cliffy/command' +import type { GlobalOptions } from '../../lib/types.ts' + +import { generate } from './generate.ts' +import { open } from './open.ts' + +export const sln = new Command() + .description('debug') + .action(function () { + this.showHelp() + }) + .command('generate', generate) + .command('open', open) diff --git a/src/commands/sln/open.ts b/src/commands/sln/open.ts new file mode 100644 index 0000000..22ce04a --- /dev/null +++ b/src/commands/sln/open.ts @@ -0,0 +1,30 @@ +import { Command } from '@cliffy/command' +import { Config } from '../../lib/config.ts' +import { createProject } from '../../lib/project.ts' +import * as path from '@std/path' +import { exec, findFilesByExtension } from '../../lib/utils.ts' + +export const open = new Command() + .description('open') + .option('--dry-run', 'Dry run', { default: false }) + .action(async (options) => { + const config = Config.getInstance() + const { engine: { path: enginePath }, project: { path: projectPath } } = config.mergeConfigCLIConfig({ + cliOptions: options, + }) + const project = await createProject(enginePath, projectPath) + + const projectSlnFiles = await findFilesByExtension( + path.join(`${project.projectFileVars.projectDir}`, '..'), + 'sln', + false, + ) + const engineSlnFiles = await findFilesByExtension(enginePath, 'sln', false) + + const slnFiles = [...projectSlnFiles, ...engineSlnFiles] + + if (slnFiles.length > 0) { + console.log(`opening ${slnFiles[0]}`) + await exec('cmd.exe', ['cmd.exe', '/c', slnFiles[0]]) + } + }) diff --git a/src/lib/engine.ts b/src/lib/engine.ts index bc050e2..2693f7f 100644 --- a/src/lib/engine.ts +++ b/src/lib/engine.ts @@ -24,6 +24,42 @@ interface EngineVersionData { "BranchName": "++UE5+Release-5.0" } */ + +export enum CookStyle { + pak = 'pak', + zen = 'zen', + nopak = 'nopak', +} + +export enum CookTarget { + Windows = 'Windows', + WindowsClient = 'WindowsClient', + WindowsNoEditor = 'WindowsNoEditor', + WindowsServer = 'WindowsServer', + Linux = 'Linux', + LinuxClient = 'LinuxClient', + LinuxNoEditor = 'LinuxNoEditor', + LinuxServer = 'LinuxServer', + Mac = 'Mac', + MacClient = 'MacClient', + MacNoEditor = 'MacNoEditor', + MacServer = 'MacServer', + Android = 'Android', + Android_ASTC = 'Android_ASTC', + Android_DXT = 'Android_DXT', + Android_ETC2 = 'Android_ETC2', + IOS = 'IOS', + PS4 = 'PS4', + PS5 = 'PS5', + Switch = 'Switch', + XboxOne = 'XboxOne', + XSX = 'XSX', + TVOS = 'TVOS', + HoloLens = 'HoloLens', + AllDesktop = 'AllDesktop', + HTML5 = 'HTML5', +} + export enum EngineConfiguration { Debug = 'Debug', DebugGame = 'DebugGame', @@ -39,8 +75,7 @@ export enum EngineTarget { Server = 'Server', } -export enum ProjectTarget { - Editor = 'Editor', +export enum GameTarget { Game = 'Game', Client = 'Client', Server = 'Server', @@ -76,7 +111,7 @@ export interface TargetInfo { } interface UBTOptions { - target: ProjectTarget | string + target: EngineTarget | string configuration?: EngineConfiguration platform?: EnginePlatform extraArgs?: string[] @@ -108,7 +143,7 @@ export abstract class Engine { abstract getGenerateScript(): string abstract getGitDependencesBin(): string abstract parseEngineTargets(): Promise - abstract getEditorBin(): string + abstract getEditorBin(cmdBin?: boolean, debug?: boolean): string abstract getEditorCmdBin(): string getEngineVersionData(): EngineVersionData { @@ -167,6 +202,34 @@ export abstract class Engine { return await exec(buildScript, args) } + async runEditor({ + useCmd = false, + dryRun = false, + debug = false, + args, + }: { + useCmd?: boolean + dryRun?: boolean + debug?: boolean + args: Array + }) { + const editorBin = this.getEditorBin(useCmd, debug) + + console.log(`Running editor with: ${editorBin} ${args.join(' ')}`) + + if (dryRun) { + return + } + + try { + const result = await exec(editorBin, args) + return result + } catch (error: unknown) { + console.log(`Error running Editor: ${error instanceof Error ? error.message : String(error)}`) + Deno.exit(1) + } + } + async ubt(args: string[] = [], options = { quiet: false }) { const buildScript = this.getBuildScript() return await exec(buildScript, args, options) @@ -238,13 +301,20 @@ class WindowsEngine extends Engine { return [] } } - override getEditorBin(): string { + override getEditorBin(cmdBin?: boolean, debug?: boolean): string { + let exeName = 'UnrealEditor' + if (cmdBin) { + exeName = exeName + '-Cmd' + } + if (debug) { + exeName = exeName + 'Win64-Debug' + } const editorPath = path.join( this.enginePath, 'Engine', 'Binaries', 'Win64', - 'UnrealEditor.exe', + `${exeName}.exe`, ) return editorPath } @@ -321,7 +391,7 @@ class MacosEngine extends Engine { return [] } } - override getEditorBin(): string { + override getEditorBin(cmdBin?: boolean, debug?: boolean): string { const editorPath = path.join( this.enginePath, 'Engine', @@ -404,7 +474,7 @@ class LinuxEngine extends Engine { return [] } } - override getEditorBin(): string { + override getEditorBin(cmdBin?: boolean, debug?: boolean): string { const editorPath = path.join( this.enginePath, 'Engine', @@ -477,15 +547,11 @@ export function getEditorPath(enginePath: string, platform: EnginePlatform): str /** * Get the platform-specific cook target */ -export function getPlatformCookTarget(platform: EnginePlatform): string { - switch (platform) { - case EnginePlatform.Windows: - return 'Windows' - case EnginePlatform.Mac: - return 'MAC' - case EnginePlatform.Linux: - return 'Linux' - default: - throw new Error(`Unsupported platform: ${platform}`) +export function getPlatformCookTarget(platform: EnginePlatform, gameTarget: GameTarget): string { + const prefix = platform + let suffix = gameTarget as string + if (gameTarget == GameTarget.Game) { + suffix = '' } + return prefix + suffix } diff --git a/src/lib/project.ts b/src/lib/project.ts index 93bd0a9..5647d5d 100644 --- a/src/lib/project.ts +++ b/src/lib/project.ts @@ -4,15 +4,16 @@ import { ValidationError } from '@cliffy/command' import { logger } from '../lib/logger.ts' import { + CookTarget, createEngine, Engine, EngineConfiguration, EnginePlatform, EngineTarget, - getPlatformCookTarget, + GameTarget, TargetInfo, } from '../lib/engine.ts' -import { copyBuildGraphScripts, exec, findProjectFile } from '../lib/utils.ts' +import { copyBuildGraphScripts, exec, findProjectFile, parseCSForTargetType } from '../lib/utils.ts' const TargetError = (target: string, targets: string[]) => { return new ValidationError(`Invalid Target: ${target} @@ -21,13 +22,10 @@ Valid Targets: ${targets.join(', ')} } const defaultBCRArgs = [ - '-build', - '-cook', '-stage', '-package', '-prereqs', '-manifests', - '-pak', '-compressed', '-nop4', '-utf8output', @@ -91,28 +89,84 @@ export class Project { extraArgs = [], dryRun = false, platform = this.engine.getPlatformName(), + clean = false, + nouht = false, + noxge = true, + projected = true, }: { - target?: EngineTarget + target?: EngineTarget | GameTarget configuration?: EngineConfiguration platform?: EnginePlatform extraArgs?: string[] dryRun?: boolean + clean?: boolean + nouht?: boolean + noxge?: boolean + projected?: boolean + }) { + const projectTarget = await this.getProjectTarget(target) + + this.compileTarget({ + target: projectTarget, + configuration: configuration, + platform: platform, + extraArgs: extraArgs, + dryRun: dryRun, + clean: clean, + nouht: nouht, + noxge: noxge, + projected: projected, + }) + } + + async compileTarget({ + target, + configuration = EngineConfiguration.Development, + extraArgs = [], + dryRun = false, + platform = this.engine.getPlatformName(), + clean = false, + nouht = false, + noxge = true, + projected = false, + }: { + target: string + configuration?: EngineConfiguration + platform?: EnginePlatform + extraArgs?: string[] + dryRun?: boolean + clean?: boolean + nouht?: boolean + noxge?: boolean + projected?: boolean }) { const args = [ - this.projectFileVars.projectArgument, '-NoUBTMakefiles', - '-NoXGE', '-NoHotReload', '-NoCodeSign', '-NoP4', '-TraceWrites', - ].concat(extraArgs) + '-Progress', + ...extraArgs, + ] - const projectTarget = `${this.projectFileVars.projectName}${target}` + if (projected) { + args.push(this.projectFileVars.projectArgument) + } + if (noxge) { + args.push('-NoXGE') + } + if (clean) { + args.push('-Clean') + } + if (nouht) { + args.push('-NoBuildUHT') + } + + await this.checkTarget(target) - await this.checkTarget(projectTarget) await this.engine.runUBT({ - target: projectTarget, + target: target, configuration: configuration, platform: platform, extraArgs: args, @@ -120,6 +174,15 @@ export class Project { }) } + async getProjectTarget(target: EngineTarget | GameTarget): Promise { + const targetResult = await this.getTargetByType(target) + if (targetResult && targetResult.targetName) { + return targetResult.targetName + } else { + return `Unreal${target}` + } + } + async package({ configuration = EngineConfiguration.Development, extraArgs = [], @@ -138,7 +201,7 @@ export class Project { dryRun?: boolean }) { const profileArgs = profiles[profile as keyof typeof profiles] || [] - const bcrArgs = Array.from(new Set([...profileArgs, ...extraArgs])) + const bcrArgs = Array.from(new Set([...profileArgs, ...extraArgs, ...defaultBCRArgs])) bcrArgs.push(this.projectFileVars.projectArgument) bcrArgs.push(`-platform=${platform}`) @@ -183,59 +246,72 @@ export class Project { } } - async compileAndRunEditor({ - extraCompileArgs = [], - extraRunArgs = [], - }: { - extraCompileArgs?: string[] - extraRunArgs?: string[] - }) { - await this.compile({ extraArgs: extraCompileArgs }) - await this.runEditor({ extraArgs: extraRunArgs }) - } - async runEditor({ - extraArgs = [], + useCmd = false, + dryRun = false, + debug = false, + extraArgs, }: { - extraArgs?: string[] + useCmd?: boolean + dryRun?: boolean + debug?: boolean + extraArgs: Array }) { - const args = [ - this.projectFileVars.projectFullPath, - ].concat(extraArgs) - - console.log(`Running editor with: ${this.engine.getEditorBin} ${args.join(' ')}`) - - try { - const result = await exec(this.engine.getEditorBin(), args) - return result - } catch (error: unknown) { - console.log(`Error running Editor: ${error instanceof Error ? error.message : String(error)}`) - Deno.exit(1) - } + await this.engine.runEditor({ + useCmd: useCmd, + dryRun: dryRun, + debug: debug, + args: [this.projectFileVars.projectFullPath, ...extraArgs], + }) } async cookContent({ + target, + cultures, + onTheFly = false, + iterate = true, + noxge = true, + debug = false, + dryRun = false, extraArgs = [], }: { + target: CookTarget extraArgs?: string[] + cultures?: Array + onTheFly?: boolean + iterate?: boolean + noxge?: boolean + dryRun?: boolean + debug?: boolean }) { - const platformTarget = getPlatformCookTarget(this.engine.getPlatformName()) const args = [ this.projectFileVars.projectFullPath, '-run=Cook', - `-targetplatform=${platformTarget}`, + `-targetplatform=${target}`, '-fileopenlog', - ].concat(extraArgs) + '-unversioned', + '-stdout', + ...extraArgs, + ] - console.log(`Running editor with: ${this.engine.getEditorCmdBin} ${args.join(' ')}`) + if (cultures && cultures.length > 0) { + const cultureArg: string = cultures.join('+') + args.push(`-cookcultures=${cultureArg}`) + } - try { - const result = await exec(this.engine.getEditorCmdBin(), args) - return result - } catch (error: unknown) { - console.log(`Error running Editor: ${error instanceof Error ? error.message : String(error)}`) - Deno.exit(1) + if (onTheFly) { + args.push('-cookonthefly') + } + + if (iterate) { + args.push('-iterate') + } + + if (noxge) { + args.push('noxge') } + + await this.engine.runEditor({ useCmd: true, dryRun: dryRun, debug: debug, args: args }) } async parseProjectTargets(): Promise { @@ -258,6 +334,48 @@ export class Project { } } + async readTargets(targetDir: string): Promise< + Array<{ + targetName: string | null + targetType: string | null + }> + > { + const targetArray: Array<{ + targetName: string | null + targetType: string | null + }> = [] + + const files = await Deno.readDir(targetDir) + for await (const file of files) { + if (file.isFile && file.name.endsWith('.cs')) { + const result = await parseCSForTargetType(path.join(targetDir, file.name)) + targetArray.push(result) + } + } + + return targetArray + } + + async getTargetByType(targetType: EngineTarget | GameTarget): Promise< + { + targetName: string | null + targetType: string | null + } | null + > { + let outTarget = null + const targetDir = path.join(this.projectFileVars.projectDir, 'Source') + + const targets = await this.readTargets(targetDir) + + targets.forEach((target) => { + if (target.targetType == targetType) { + outTarget = target + } + }) + + return outTarget + } + async genProjectFiles(args: string[]) { // Generate project // GenerateProjectFiles.bat -project=E:\Project\TestProject.uproject -game -engine @@ -284,16 +402,6 @@ export class Project { } } - async runPython(scriptPath: string, extraArgs: Array) { - const args = [ - '-run=pythonscript', - `-script=${scriptPath}`, - ...extraArgs, - ] - logger.info(`Running Python script: ${scriptPath}`) - await this.runEditor({ extraArgs: args }) - } - async runBuildGraph(buildGraphScript: string, args: string[] = []) { let bgScriptPath = path.resolve(buildGraphScript) if (!bgScriptPath.endsWith('.xml')) { diff --git a/src/lib/types.ts b/src/lib/types.ts index 96cdf78..603a1ab 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -4,7 +4,7 @@ import type { $ } from '@david/dax' import type { z } from 'zod' import type { cmd } from '../cmd.ts' -import type { DebugConfigOptions } from '../commands/debug/debug-config.ts' +import type { DebugConfigOptions } from '../commands/info/config.ts' import type { SetupOptions } from '../commands/engine/setup.ts' import type { InstallOptions } from '../commands/engine/install.ts' import type { UpdateOptions } from '../commands/engine/update.ts' diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 20d91f4..cd54616 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -410,3 +410,63 @@ ${blueprint} ` return html } + +export async function findFilesByExtension( + rootDir: string, + extension: string, + recursive: boolean, +): Promise { + const files: string[] = [] + + try { + for await (const entry of Deno.readDir(rootDir)) { + const checkPath = `${rootDir}/${entry.name}` + + if (entry.isDirectory && recursive) { + const subFiles = await findFilesByExtension(checkPath, extension, recursive) + files.push(...subFiles) + } else if (entry.isFile && checkPath.endsWith(extension)) { + files.push(checkPath) + } + } + } catch (error) { + console.error(`Error reading directory ${rootDir}:`, error) + } + + return files +} + +export async function parseCSForTargetType(filePath: string): Promise<{ + targetName: string | null + targetType: string | null +}> { + // Read the file + const fileContent = await Deno.readTextFile(filePath) + + // Results object + const result = { + targetName: null as string | null, + targetType: null as string | null, + } + + // Find the class name using regex + const classRegex = /class\s+(\S+)Target[\s:]/g + let classMatch + + while ((classMatch = classRegex.exec(fileContent)) !== null) { + result.targetName = classMatch[1] + break // Get only the first class name + } + + // Find variables named TargetType + // This pattern looks for field declarations that have 'TargetType' as variable name + const targetTypeRegex = /\s*TargetType\.(.+)\s*;/g + let targetTypeMatch + + while ((targetTypeMatch = targetTypeRegex.exec(fileContent)) !== null) { + result.targetType = targetTypeMatch[1] + break + } + + return result +}