Skip to content

Commit 034e4b2

Browse files
committed
enhancement: better compilation command
1 parent 82a0f3a commit 034e4b2

File tree

2 files changed

+844
-12
lines changed

2 files changed

+844
-12
lines changed

src/commands/compile.ts

Lines changed: 245 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/* eslint-disable no-console */
22
import {Command} from 'commander'
33
import {execSync} from 'child_process'
4-
import {existsSync, readdirSync} from 'fs'
5-
import {basename, extname, join, resolve} from 'path'
4+
import {existsSync, mkdirSync, readdirSync, readFileSync, statSync} from 'fs'
5+
import {basename, dirname, extname, join, relative, resolve} from 'path'
66
import {checkLeapInstallation} from './chain/install'
77

88
/**
@@ -25,19 +25,55 @@ export async function compileContract(file: string | undefined, outputDir: strin
2525
// Ensure cdt-cpp is installed
2626
await ensureCdtCppInstalled()
2727

28-
console.log(`Compiling ${files.length} file(s) to ${absoluteOutputDir}\n`)
28+
console.log(`Compiling ${files.length} file(s)...\n`)
2929

3030
for (const filePath of files) {
31-
await compileSingleFile(filePath, absoluteOutputDir)
31+
await compileSingleFile(filePath, currentDir, absoluteOutputDir)
3232
}
3333

3434
console.log('\nCompilation complete!')
3535
}
3636

37+
/**
38+
* Recursively find all .cpp files in a directory
39+
*/
40+
function findCppFilesRecursive(dir: string, files: string[] = []): string[] {
41+
try {
42+
const entries = readdirSync(dir, {withFileTypes: true})
43+
44+
for (const entry of entries) {
45+
const fullPath = join(dir, entry.name)
46+
47+
// Skip hidden directories and common build/output directories
48+
if (entry.isDirectory()) {
49+
if (
50+
entry.name.startsWith('.') ||
51+
entry.name === 'node_modules' ||
52+
entry.name === 'build' ||
53+
entry.name === 'dist' ||
54+
entry.name === 'lib'
55+
) {
56+
continue
57+
}
58+
findCppFilesRecursive(fullPath, files)
59+
} else if (entry.isFile() && extname(entry.name) === '.cpp') {
60+
files.push(fullPath)
61+
}
62+
}
63+
} catch {
64+
// Ignore permission errors and other filesystem errors
65+
}
66+
67+
return files
68+
}
69+
3770
/**
3871
* Get list of files to compile
3972
*/
40-
async function getFilesToCompile(file: string | undefined, currentDir: string): Promise<string[]> {
73+
export async function getFilesToCompile(
74+
file: string | undefined,
75+
currentDir: string
76+
): Promise<string[]> {
4177
if (file) {
4278
const filePath = resolve(currentDir, file)
4379
if (!existsSync(filePath)) {
@@ -49,10 +85,8 @@ async function getFilesToCompile(file: string | undefined, currentDir: string):
4985
return [filePath]
5086
}
5187

52-
// Get all .cpp files in current directory
53-
const files = readdirSync(currentDir)
54-
.filter((f) => extname(f) === '.cpp')
55-
.map((f) => join(currentDir, f))
88+
// Recursively find all .cpp files in current directory and subdirectories
89+
const files = findCppFilesRecursive(currentDir)
5690

5791
return files
5892
}
@@ -104,18 +138,217 @@ function isCdtCppInstalled(): boolean {
104138
}
105139
}
106140

141+
/**
142+
* Find the contract root directory by walking up from the source file
143+
* Looks for common contract directory structures
144+
*/
145+
export function findContractRoot(sourceFile: string): string {
146+
let currentDir = dirname(resolve(sourceFile))
147+
const root = resolve('/')
148+
149+
// Walk up the directory tree looking for contract structure indicators
150+
while (currentDir !== root) {
151+
// Check for common contract directory patterns
152+
const hasInclude = existsSync(join(currentDir, 'include'))
153+
const hasSrc = existsSync(join(currentDir, 'src'))
154+
const hasInc = existsSync(join(currentDir, 'inc'))
155+
const hasHeaders = existsSync(join(currentDir, 'headers'))
156+
157+
// If we find include directories or src directory, this might be the contract root
158+
if (hasInclude || hasSrc || hasInc || hasHeaders) {
159+
return currentDir
160+
}
161+
162+
// Also check if current directory contains .cpp files (might be contract root)
163+
try {
164+
const files = readdirSync(currentDir)
165+
const hasCppFiles = files.some((f) => extname(f) === '.cpp')
166+
if (hasCppFiles && (hasInclude || hasInc || hasHeaders)) {
167+
return currentDir
168+
}
169+
} catch {
170+
// Ignore permission errors
171+
}
172+
173+
currentDir = dirname(currentDir)
174+
}
175+
176+
// If no contract root found, return the source file's directory
177+
return dirname(resolve(sourceFile))
178+
}
179+
180+
/**
181+
* Auto-detect include directories
182+
*/
183+
export function detectIncludeDirectories(sourceFile: string, contractRoot: string): string[] {
184+
const sourceDir = dirname(resolve(sourceFile))
185+
const includeDirs: string[] = []
186+
187+
// Common include directory patterns
188+
const patterns = [
189+
join(contractRoot, 'include'),
190+
join(contractRoot, 'inc'),
191+
join(contractRoot, 'headers'),
192+
join(sourceDir, 'include'),
193+
join(sourceDir, 'inc'),
194+
join(sourceDir, 'headers'),
195+
]
196+
197+
for (const dir of patterns) {
198+
if (existsSync(dir) && statSync(dir).isDirectory()) {
199+
const normalized = resolve(dir)
200+
if (!includeDirs.includes(normalized)) {
201+
includeDirs.push(normalized)
202+
}
203+
}
204+
}
205+
206+
return includeDirs
207+
}
208+
209+
/**
210+
* Auto-detect resource paths (for quoted includes like #include "actions/commit.cpp")
211+
*/
212+
export function detectResourcePaths(sourceFile: string, contractRoot: string): string[] {
213+
const sourceDir = dirname(resolve(sourceFile))
214+
const resourcePaths: string[] = []
215+
216+
// Common resource directory patterns
217+
const patterns = [
218+
join(contractRoot, 'src'),
219+
join(sourceDir, 'src'),
220+
contractRoot, // Root itself might contain resources
221+
sourceDir, // Source directory might contain resources
222+
]
223+
224+
for (const dir of patterns) {
225+
if (existsSync(dir) && statSync(dir).isDirectory()) {
226+
const normalized = resolve(dir)
227+
if (!resourcePaths.includes(normalized)) {
228+
resourcePaths.push(normalized)
229+
}
230+
}
231+
}
232+
233+
return resourcePaths
234+
}
235+
236+
/**
237+
* Parse #include statements from source file
238+
*/
239+
export function parseIncludes(sourceFile: string): {angleBracket: string[]; quoted: string[]} {
240+
const content = readFileSync(sourceFile, 'utf8')
241+
const angleBracket: string[] = []
242+
const quoted: string[] = []
243+
244+
// Match #include <...> and #include "..."
245+
const includeRegex = /#include\s+[<"]([^>"]+)[>"]/g
246+
let match
247+
248+
while ((match = includeRegex.exec(content)) !== null) {
249+
const includePath = match[1]
250+
if (match[0].includes('<')) {
251+
angleBracket.push(includePath)
252+
} else {
253+
quoted.push(includePath)
254+
}
255+
}
256+
257+
return {angleBracket, quoted}
258+
}
259+
260+
/**
261+
* Auto-detect compilation flags based on source file and directory structure
262+
*/
263+
export function autoDetectCompileFlags(sourceFile: string): string[] {
264+
const contractRoot = findContractRoot(sourceFile)
265+
const includeDirs = detectIncludeDirectories(sourceFile, contractRoot)
266+
const resourcePaths = detectResourcePaths(sourceFile, contractRoot)
267+
const includes = parseIncludes(sourceFile)
268+
269+
const flags: string[] = []
270+
271+
// Add include directories (-I flag) if they exist
272+
// These are needed for angle-bracket includes like <randomrng/randomrng.hpp>
273+
if (includeDirs.length > 0) {
274+
for (const dir of includeDirs) {
275+
flags.push(`-I${dir}`)
276+
}
277+
}
278+
279+
// Add resource paths (-R flag) for quoted includes like "actions/commit.cpp"
280+
// Only add if there are actually quoted includes in the source
281+
if (resourcePaths.length > 0 && includes.quoted.length > 0) {
282+
for (const path of resourcePaths) {
283+
flags.push(`-R${path}`)
284+
}
285+
}
286+
287+
return flags
288+
}
289+
107290
/**
108291
* Compile a single C++ file to WASM using cdt-cpp
109292
*/
110-
async function compileSingleFile(filePath: string, outputDir: string): Promise<void> {
293+
async function compileSingleFile(
294+
filePath: string,
295+
currentDir: string,
296+
outputDir: string
297+
): Promise<void> {
111298
const fileName = basename(filePath, '.cpp')
112-
const wasmOutput = join(outputDir, `${fileName}.wasm`)
299+
const contractRoot = findContractRoot(filePath)
300+
const contractRootSrc = join(contractRoot, 'src')
301+
const sourceFileAbsolute = resolve(filePath)
302+
303+
// Check if source file is inside a src/ directory relative to contract root
304+
// If so, strip the src/ prefix from output path
305+
let relativePath: string
306+
if (existsSync(contractRootSrc) && sourceFileAbsolute.startsWith(resolve(contractRootSrc))) {
307+
// File is in src/ directory, calculate path relative to contract root
308+
const pathFromContractRoot = relative(contractRoot, filePath)
309+
// Strip 'src/' prefix if present
310+
if (pathFromContractRoot.startsWith('src/')) {
311+
relativePath = pathFromContractRoot.substring(4) // Remove 'src/' prefix
312+
} else {
313+
relativePath = pathFromContractRoot
314+
}
315+
} else {
316+
// Use path relative to current directory (preserve structure)
317+
relativePath = relative(currentDir, filePath)
318+
}
319+
320+
const relativeDir = dirname(relativePath)
321+
322+
// Calculate output path
323+
let wasmOutput: string
324+
if (relativeDir === '.' || relativeDir === '') {
325+
// File should be output directly in output directory
326+
wasmOutput = join(outputDir, `${fileName}.wasm`)
327+
} else {
328+
// File is in a subdirectory, preserve the structure (but without src/ prefix if stripped)
329+
const outputSubDir = join(outputDir, relativeDir)
330+
// Ensure the output subdirectory exists
331+
if (!existsSync(outputSubDir)) {
332+
mkdirSync(outputSubDir, {recursive: true})
333+
}
334+
wasmOutput = join(outputSubDir, `${fileName}.wasm`)
335+
}
336+
337+
// Calculate ABI output path (same location as WASM, but with .abi extension)
338+
const abiOutput = wasmOutput.replace(/\.wasm$/, '.abi')
113339

114340
console.log(`Compiling: ${filePath}`)
115341
console.log(`Output: ${wasmOutput}`)
116342

343+
// Auto-detect compilation flags
344+
const autoFlags = autoDetectCompileFlags(filePath)
345+
if (autoFlags.length > 0) {
346+
console.log(`Auto-detected flags: ${autoFlags.join(' ')}`)
347+
}
348+
117349
try {
118-
const command = `cdt-cpp -abigen -o "${wasmOutput}" "${filePath}"`
350+
const flagsStr = autoFlags.length > 0 ? `${autoFlags.join(' ')} ` : ''
351+
const command = `cdt-cpp -abigen -abigen_output="${abiOutput}" ${flagsStr}-o "${wasmOutput}" "${filePath}"`
119352
execSync(command, {
120353
stdio: 'inherit',
121354
cwd: process.cwd(),

0 commit comments

Comments
 (0)